Kembali ke Daftar Catatan

Streaming di Next.js

#nextjs#streaming

Ketika sebuah halaman membutuhkan waktu lama untuk fetching data, kita bisa memberikan efek loading agar pengalaman pengguna tetap nyaman.

Streaming Tingkat Halaman

Next.js menyediakan cara mudah untuk menampilkan loading saat data sedang dimuat. Cukup buat file loading.tsx di dalam folder route yang sesuai.

Contoh

// app/posts/[slug]/loading.tsx
export default function Loading() {
  return <div>Loading...</div>;
}

File loading.tsx akan otomatis ditampilkan saat halaman DetailPostPage masih dalam proses fetching data.

Simulasi Fetching Data Lama

Untuk melihat efek loading secara nyata, kita bisa mensimulasikan delay dengan setTimeout.

Contoh

export default async function DetailPostPage({
  params,
}: {
  params: { slug: string };
}) {
  // Simulasi loading selama 5 detik
  await new Promise((resolve) => setTimeout(resolve, 5000));

  const postData = await getPost(params.slug);

  return (
    <div>
      <h2>{postData.title}</h2>
      <p>{postData.content}</p>
    </div>
  );
}

Streaming komponen spesifik

Mari buat file baru

// content.tsx
async function fakeDelay() {
  await new Promise((resolve) => setTimeout(resolve, 5000));
}
export default async function Content() {
  await fakeDelay();
  return <div>ngetes loading</div>;
}

Tambahkan komponen di page.tsx

import { PostData } from '@/app/type/Post';
import type { Metadata } from 'next';
import Content from './content';

export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}): Promise<Metadata> {
  const post = await getPost(params.slug);
  return { title: post.slug, description: post.content };
}

async function getPost(slug: string): Promise<PostData> {
  const res = await fetch(`http://localhost:3001/post/?slug=` + slug);
  const [post] = await res.json();
  return post;
}

export default async function DetailPostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  // await new Promise((resolve) => setTimeout(resolve, 5000));
  const { slug } = await params;
  const postData = await getPost(slug);

  return (
    <div>
      ini detailnya
      <h2>{postData.title}</h2>
      <p>{postData.content}</p>
      <Content />
    </div>
  );
}

Nah, ketika dicoba kembali, komponen content.tsx menyebabkan semua menjadi loading. Hal ini disebabkan loading.tsx juga berlaku di bawah direktori yang sama.

Kita coba membungkus dengan Suspense

import { PostData } from '@/app/type/Post';
import type { Metadata } from 'next';
import Content from './content';
import { Suspense } from 'react';

export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}): Promise<Metadata> {
  const post = await getPost(params.slug);
  return { title: post.slug, description: post.content };
}

async function getPost(slug: string): Promise<PostData> {
  const res = await fetch(`http://localhost:3001/post/?slug=` + slug);
  const [post] = await res.json();
  return post;
}

export default async function DetailPostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  // await new Promise((resolve) => setTimeout(resolve, 5000));
  const { slug } = await params;
  const postData = await getPost(slug);

  return (
    <div>
      ini detailnya
      <h2>{postData.title}</h2>
      <p>{postData.content}</p>
      <Suspense fallback={<p>loading content</p>}>
        <Content />
      </Suspense>
    </div>
  );
}

Nah, jadi selain komponen Content di-preview dahulu sembari komponen Content selesai di-fetching.

Catatan Terkait