skip to Main Content

I’m experimenting with nextjs (coming from react). I think I’m mostly getting things working, but when I create a server component to test fetching on the server side, I’m getting odd results.

Most of the time, when I start the server and load the page the Loading... message shows all the time. Then if I refresh the page, the data displays as expected. Navigating around the site and returning to the page usually shows the data, but sometimes shows Loading and if that happens, I have to refresh the page for the data to show again.

I suspect it’s something to do with the nextjs caching, but I’m not sure.

My page component looks like this, roughly stolen from the nextjs documentation:

let statsData: StatsType[];
let url = 'stats';

async function getData() {
    const res = await fetchDataServerSide(url);

    if (!res.ok) {
        throw new Error('Failed to fetch data')
    }
    await res.json().then(json => {
        statsData = json;
    })
}

const Dashboard = () => {
    const data = getData();

    /*if (error) return <div>Failed to load</div>*/
    if (!statsData) {
        return <div>Loading...</div>
    } else {
        return (
            <div className={"p-6"}>
                <div className={"pb-6"}>
                    <Stats stats={statsData}/>
                </div>
            </div>
        )
    }
}

export default Dashboard

my fetchDataServerSide was just an attempt to abstract the fetch from each page I need to pull data, since I need to pass in headers each time to work with my laravel API backend:

async function fetchDataServerSide(url: string): Promise<Response> {
    'use server'
    let fetch_path = process.env.NEXT_PUBLIC_BACKEND_URL + '/api/' + url

    const options: RequestInit = {
        method: "GET",
        headers: {
            "Accept": "application/json",
            "Referer": process.env.APP_URL,
            "X-Requested-With": "XMLHttpRequest",
            "Content-Type": "application/json",
            "cookie": "XSRF-TOKEN=" + cookies().get('XSRF-TOKEN').value + ";laravel_session=" + cookies().get('laravel_session').value,
        }
    }

    return await fetch(fetch_path, options);
}

Any thoughts on what I’m doing wrong?

2

Answers


  1. You are not waiting for the data to be fetched. You need to change the "Dashboard" to be an async component and use suspense boundary with a fallback loading UI. It will display loading UI until data needed by the children has been loaded. When the data is loaded, Stats component is rendered with data. The await component is used to wait for the data to be fetched and after fetching, it return the data to the children.

    import { notFound } from "next/navigation";
    import { Suspense } from 'react';
    
    async function Await<T>({ promise, children }: { promise: Promise<T>, children: (value: T) => JSX.Element }) {
      let data = await promise;
      if (!data) return notFound();
      return children(data);
    }
    
    const Dashboard = async () => {
      const data = getData();
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <Await promise={data}>
            {( data ) =>  <Stats stats={data}/>}
          </Await>
        </Suspense>
      );
    };
    
    Login or Signup to reply.
  2. You need to convert Dashboard component to async, it’s ok since it’s a server component.
    Then await your getData() to conditionally render your Stats component.

    const data = await getData();
    

    Then, if you want to keep a loading view, you can create a loading.js skeleton in the same route folder :

    export default function Loading() { 
       //custom loading skeleton component
       return <p>Loading...</p>
    }
    

    https://nextjs.org/docs/app/api-reference/file-conventions/loading

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search