Next.js Discord

Discord Forum

Suspense fallback is not working in nextjs 14

Unanswered
Segugio Italiano posted this in #help-forum
Open in Discord
Segugio ItalianoOP
When I am routing to this route using next link, my page is blocked from rendering for 2 seconds. Instead I want to show the headings and their own fallbacks as a loading state untill the promise is resolved or I get data from the database.

loading.tsx in the same level is working but that will allow to render the whole page to show the loader which I do not want. The headings or other static text should be there and the data which is fetched from database should have that loading state untill data is fetched.


 import React, { Suspense } from "react"
export const dynamic = "force-dynamic"

const wait = async () => {
  let data = {
    data: "some data",
  }
  await new Promise((resolve) => setTimeout(resolve, 2000))
  return data
}

const TestRoute = async () => {
  let { data } = await wait()
  console.log(data)
  return (
    <>
      <h3>Title 1</h3>
      <Suspense fallback={<div className="text-red-600">Loading...</div>}>
        <p>some delayed text</p>
      </Suspense>
      <h3>Title 2</h3>
      <Suspense fallback={<div className="text-red-600">Loading2...</div>}>
        <p>some delayed text</p>
      </Suspense>
    </>
  )
}

export default TestRoute

14 Replies

Are you expecting the <p>some delayed text</p> to be delayed?

Because what you are showing here is a page which instantly calls a promise - resulting in block time on page render.

If you want to use the Suspense like you are trying to do here, you will have to make that async call in another component, which is then wrapped in the Suspense like:

export default function Page() {
  return (
    <Suspense>
      <SomeServerComponent />
    </Suspense>
  )
}

export async function SomeServerComponent() {
  await wait();

  return (
    <p>some delayed text</p>
  )
}
Segugio ItalianoOP
What about this approach

  import React, { Suspense } from "react"
import Await from "./await"
export const dynamic = "force-dynamic"

const wait = async () => {
  let data = {
    data: "some data",
  }
  await new Promise((resolve) => setTimeout(resolve, 2000))
  return data
}

const TestRoute = async () => {
  let promise = wait()
  return (
    <>
      <h3>Title 1</h3>
      <Suspense fallback={<div>Loading....</div>}>
        <Await promise={promise}>{({ data }) => <p>{data}</p>}</Await>
      </Suspense>
    </>
  )
}

export default TestRoute



  interface AwaitProps<T> {
  promise: Promise<T>
  children: (value: T) => JSX.Element
}

const Await = async <T,>({ promise, children }: AwaitProps<T>) => {
  let data = await promise
  return children(data)
}

export default Await



If I have to fetch the data in the parent only?

@Paul.
You mean your Await component doesn't have access to the promise?
Nile Crocodile
Try with await use(promise) in your Await
It might need to run on the client
@Nile Crocodile Try with `await use(promise)` in your `Await`
The entire idea of the OP was to stream the component, by turning it into a client component that kinda defeats the purpose right?
Nile Crocodile
The data will be streamed, but it will show the Suspense fallback until it ready.. Otherwise it will need to wait for the server..
The loading.tsx is for the entire page, whereas they want to show a loading a specific place.
Yeah I got that part - so they shouldn't need the use. Just the await promise() should be enough for streaming to work
Nile Crocodile
Without use I think it will block the render until it have been resolved which means the fallback will not show.
Segugio ItalianoOP
the approach I posted above kind of working. But one issue like somehow data is cached. If I am coming to this route then the for the first time loading state is perfectly working but next time I am navigating to this route the data is directly shown.

How can I change this behaviour if I need fresh data always?
You can use revalidation https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating. But that only works on page-level not on component-level.
Segugio ItalianoOP
ok thanks then in my case, I can use like this

import { GET_PROFILE } from "../../../graphql/queries"

const fetchProfile = async () => {
  const response = await fetch(process.env.URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: `
        query Profile {
          profile {
            id
            name
          }
        }`,
    }),
    next: { revalidate: 0 },
  })
  const data = await response.json()

  return {
    profile: data?.data?.profile || null,
  }
}

export default fetchProfile


and then use directly in the component?
that should work - yes