Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Server side rendering with dynamic client side using nextjs App Router

A pattern that I've used a ton with NextJs is to render everything server side (including data fetching) and then transition to dynamic client side rendering. Contrived example:

export async function getServerSideProps() {
  const res = await fetch(`api/myData`)
  const data = await res.json()

  return {
    props: { initialData: [data] }
  }
}

export default function About({ initialData }) {
  const [data, setData] = useState(initialData)

  const addItem = () => {
    const item = "foo"
    const res = await fetch('/api/addData')
    setData([...data, JSON.parse(res.body).item])
  }

  return (
    <div>
      {data.map(item => (<div>{item}</div>))}
      
      <button onClick={addItem}>Add Item</button>
    </div>
  )
}

And I cannot figure out how I can get the same sort of functionality with the new App Router style of building NextJs. Can anyone show me what I am missing?

like image 797
Ryan O'Neill Avatar asked May 19 '26 21:05

Ryan O'Neill


1 Answers

Let's break this down by steps:

  1. You want to fetch the initalData server side dynamically, that means that you need to fetch your data in a server component, I think the page.tsx component is a pretty good place to achieve this, so an example page component would be:

    export default async function Page() {
      const res = await fetch(`api/myData`, {cache: 'no-store'});
      const data = await res.json();
    
      return <About initialData={[data]} />
    }
    

    note that fetch has cache: 'no-store', this is needed in order to opt into dynamic rendering since static rendering is the default, this could also be achieved with revalidate: 0.

  2. You need to make <About /> a client component in order to be able to use useState etc. So you need to add 'use client' at the top of your About component file.

One thing to keep in mind is that in a real world application you may want your page to be statically rendered, a solution is to encapsulate what we did into a separate server component and have that server component to fetch the data and dynamically render instead of the whole page component.

Also regarding the dynamic between client and server components. Client components are in fact prerendered in the server (html/css and a small js bundle), but they also require a js payload that cannot run in the server, if you think about it, it makes no sense to run useState in the server since the state lives in the user browser, even less to have an onClick listener in the server. When nextjs prerenders a client component it sends back the rendered html/css/server js and the client js payload that need to be executed in the client. Server components in the other hand doesn't require that client js and so only html/css and a small js is returned from the server. In your original example getServerSideProps is executed in the server but the <About /> component has a client payload just like a component with use client with the new app folder router. Having a component with client code like useState and onClick rendered 100% in the server is currently not supported and makes zero sense. Regarding html and css as I said before, it is initially prerendered in the server for clients component as well. You can read more about it here https://nextjs.org/docs/getting-started/react-essentials#client-components

like image 89
alexortizl Avatar answered May 22 '26 23:05

alexortizl