I am using nextjs v15. I have some pages where my content is fully static, and as such I want to pre-render it. However, in my layout.tsx, each page also has my header component which is not static, and retrieves auth data that has headers/cookies.
If I attempt to export dynamic to 'error' as such:
export dynamic = 'error';
which will try to force the page to be rendered statically, and will error out if it can't, then I get this:
Error: Route / with `dynamic = "error"` couldn't be rendered statically because it used `headers`. See more info here:
https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
and then some reference code to my layout.tsx where I include the auth()
call.
Is there a way to prerender statically the actual page, but not the header? Any other alternatives?
Instead of getting the authentication on the server, I could just have my header / client component do the auth call, but then my header has a loading state waiting for the authentication. I don't want that, and just want my header/authentication to be fully rendered by the time it is sent to the user.
To make your page static, you should not be using a Dynamic API and cache your data
An example
interface Post {
id: string
title: string
content: string
}
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', { cache: 'force-cache' })
const posts: Post[] = await data.json()
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
If you want your header to be visible without waiting your auth call to finish, consider Streaming
Streaming enables you to progressively render UI from the server. Work is split into chunks and streamed to the client as it becomes ready. This allows the user to see parts of the page immediately, before the entire content has finished rendering.
To do that, define your Header component as a client component and pass a server component in which you do your auth call as a prop to it (supported pattern)
Your component in which you do your auth call
"use server"
function wait(milliseconds: number) {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
const SC = async () => {
await wait(3000) // to show a progressive loading in your header, replace with auth call
return (
<div style={{ background: "green", width: "100%", height: "50px" }}>
Dynamic Part of the Header
</div>
)
}
export default SC
Your header component
"use client"
import { Suspense } from "react"
export default function Header({ children }: { children: React.ReactNode }) {
return (
<div>
<div style={{ background: "red", width: "100%", height: "20px" }}>
Static Part of the Header
</div>
<Suspense fallback={<p>Loading...</p>}>
{children}
</Suspense>
</div>
)
}
Your layout
import Header from "./ui/Header"
import SC from "./ui/SC"
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body>
<Header>
<SC />
</Header>
{children}
</body>
</html>
)
}
Application starts...
and after 3 seconds
As you see, our header is rendered from the very beginning to the end except the part we make an auth call. I have used a simple paragraph element but you can use a skeleton like in the real world examples.
Useful links