Why Next.js Suspense isn't working in this case?

134 views Asked by At

I'm working on a pretty standard Next.js 13.5.6 project which utilises app router directory with Strapi CMS as a backend. Project is NX monorepo, if that matters.

I have a page which renders a list of articles with filters and pagination where I want streaming to occur. Project structure is nothing special, page itself is in:

/app/[locale]/articles/news/page.tsx

[locale] folder is used for next-intl, which is pretty standard library as well. Page code goes as follows:

import { ArticlesLayout } from "@/components/layouts";
import { Header, Footer } from "@/components/widgets";
import { getArticlePage } from "@/api"; 

type PageProps = {
  // ...
}

export default async function Page ({ params: { locale }, searchParams }: PageProps) {
  // getting Page specific data: page header, footer, breadcrumbs, etc.
  // function itself is just a wrapper around Next's fetch with preconfigured path and auth params
  const data = await getArticlePage({ locale });

  return (
    <>
      <Header props={data.header} />
        <ArticlesLayout locale={locale} searchParams={searchParams} />
      <Footer props={data.footer} />
    <>
  );
}

So the page itself fetches some data, renders Header and Footer components and passes some data like searchQuery and pagination to ArticlesLayout. Code again follows the documentation:

import { Suspense } from "react";
import { ArticleFilter } from "@/components/widgets";
import { ArticleList } from "@/components/listings";
import { SkeletonList } from "@/components/listings";

type LayoutProps = {
  // ...
}

export async function ArticlesLayout({ locale, searchParams }: LayoutProps) {
  const query = searchParams.query;

  return (
     <section>
       <ArticleFilter />
       <Suspense key={query} fallback={<SkeletonList />}>
         <ArticleList query={query} />
       </Suspense>
     </section>
  );
}

Instead of using loading.tsx for the whole page (since Page itself fetches data only once and almost imediately) I want to stream the suspended content so according to the docs I import Suspense from React and wrap my ArticleList component with it, passing SkeletonList as a fallback.

ArticleFilter is a client component which uses useSearchParams hook to modify searchParams depending on checkbox set. And the ArticleList itself is another server component that fetches the data:

import { getArticles } from "@/api";

export async function ArticleList({ query }: { query: string }) {
  const articles = await getArticles(query);

  return (
    <>
      {/* list markup here */}
    <>
  );
}

Clicking a checkbox modifies url, that makes a new request. I expect that while await inside ArticleList is in effect, it will be replaced by a SkeletonList since it was passed to Suspense as a fallback. But instead nothing happens until promises is awaited and then the list just rerenders immediately with new data.

I tried passing revalidate: 0 to fetch, using cache: 'no-store' and even upgrading to Next 14 to add unstable_nostore to add noStore() call to my fetch functions. It doesn't help. What can be the cause of such behavior and how do find the reason of it? Maybe there's a way to debug why suspense isn't taking effect?

0

There are 0 answers