Next.js Discord

Discord Forum

Why is my page rendering dynamically?

Answered
Gray-tailed Tattler posted this in #help-forum
Open in Discord
Gray-tailed TattlerOP
I'm not doing any of these things
cookies()
headers()
searchParams
fetch(url, { cache: 'no-store' })
export const dynamic = 'force-dynamic'
export const revalidate = 0


And my page is static content
import Container from "@/components/layout/container"

export default function Page() {
  return (
    <Container>
      <div className="prose mt-8">
        <h1 className="text-2xl font-bold">Welcome to the Example Page</h1>
        <p className="mt-4">This is a sample page to demonstrate the layout and components.</p>
        <h2>
          Explore the features and customize your experience!
        </h2>
        <p>
          Lorem ipsum dolor sit amet consectetur
        </p>
      </div>
    </Container>
  )
}


On build, all my pages are rendered dynamic.
Answered by Gray-tailed Tattler
changed that server component to a client component and now i'm getting ssg routes
View full answer

96 Replies

Gray-tailed TattlerOP
import { cn } from "@/lib/utils"

type ContainerProps = {
  children: React.ReactNode
  className?: string
}

export default function Container({ children, className }: ContainerProps) {
  return (
    <div
      className={cn([
        'px-4',
        'sm:px-6',
        className
      ])}
    >
      <div className="max-w-7xl mx-auto">
        {children}
      </div>
    </div>
  )
}
Gray-tailed TattlerOP
I integrated supabase auth. There's proxy middleware if that makes a difference
Gray-tailed TattlerOP
Here's my layout
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { AuthListener } from "@/components/auth-provider"
import "./globals.css"
import Header from "@/components/layout/header"
import Footer from "@/components/layout/footer"

const defaultUrl = process.env.VERCEL_URL
  ? `https://${process.env.VERCEL_URL}`
  : "http://localhost:3000"

export const metadata: Metadata = {
  metadataBase: new URL(defaultUrl),
  title: "Next.js and Supabase Starter Kit",
  description: "The fastest way to build apps with Next.js and Supabase",
  robots: {
    index: false,
    follow: false,
    googleBot: {
      index: false,
      follow: false,
      noimageindex: true,
      'max-video-preview': -1,
      'max-snippet': -1,
    },
  }
}

const interSans = Inter({
  variable: "--font-inter-sans",
  display: "swap",
  subsets: ["latin"],
})

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en" suppressHydrationWarning data-theme="bumblebee">
      <body className={`${interSans.className} antialiased`}>
        <Header />
        {children}
        <AuthListener />
        <Footer />
      </body>
    </html>
  )
}
I suspect it's the auth listener doing a authentication check on the cookies
If anything on the layout checks cookies on the server, then every page would be dynamic
Can it be converted to work as a client component is that is the case? If it's calling auth can we use a client side hook?
Gray-tailed TattlerOP
i dont think i need the auth listener. i think it wasj ust for debugging

'use client'

import { createClient } from '@/lib/supabase/client'
import { useEffect } from 'react'

export function AuthListener() {
  useEffect(() => {
    const supabase = createClient()

    // Listen to auth changes
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((event, session) => {
      if (event === 'SIGNED_IN') {
        console.log('User signed in:', session?.user)
      }
      if (event === 'SIGNED_OUT') {
        console.log('User signed out')
      }
      if (event === 'TOKEN_REFRESHED') {
        console.log('Token refreshed')
      }
    })

    // Cleanup subscription on unmount
    return () => {
      subscription.unsubscribe()
    }
  }, [])

  return null
}
Comment that as see if it makes a difference
Run a build and see if that changes the output
Lol also what's in the header ?
We'll figure this out
Even better if it's open source send me the repo
Gray-tailed TattlerOP
it's a private repo but nothing too sensitve
one sec
I'm taking a look right now
All right. I've been playing around and when I comment the header out the page is static
So it's something inside your header causing it to be dynamic. I will investigate further
<AuthButton /> inside the header is causing the issue
Gray-tailed TattlerOP
because it's fetching from the server on every route?

  const supabase = await createClient();

  // You can also use getUser() which will be slower.
  const { data } = await supabase.auth.getClaims();

  const user = data?.claims;
yes and checking cookies im assuming
Gray-tailed TattlerOP
yeah now they're static
I havnt uses supabase b4, do they have any client side hooks you can use for fetching the user on the client?
I'm checking docs but its a slow go lol
maybe this?
Gray-tailed TattlerOP
yeah i have both server and client ...clients set up in the project. using client side client willi allow for static pages?
unless you check the date in the client component it should be static
not as good performance but useful when using the component on the layout
Because that dynamic component is on the layout it was opting all your pages to dynamic
if you fetch the data on the client should be problem solved
Gray-tailed TattlerOP
alright thx. i'lll work on moving it to the client
rad have a great night
Gray-tailed TattlerOP
you too
even if you make an api route and call that on the client, all the route would have to do is check the auth and decied what info to send back
Thai
Interestingly enough I have the opposite problem… NextJS 16.1 did something weird I think, my layout.tsx normally rendered dynamically and did not cache, however, I’ve isolated NextJS 16.1.x to be causing unwanted caching
Non 16.1 my cache-control header looks like this:

private, no-cache, no-store, max-age=0, must-revalidate
16.1+ is caching for like a year…
if you fetch on the server its dynamic if you fetch on the client it can be static
away of thinking about it is when it comes from the server the html doesnt change if there no dynamic data ei .. cookies or db call, so even tho you in the method you want to fetch the data on the client its considered static because the html css and js sent to the client doesnt change
@Thai 16.1+ is caching for like a year…
I've been using cachComponents since 16 and b4 when it was called ppr. I'm a big fan becuase you can have dynamic data but serve users a static page becuase its cached acrosse builds and when you cant cache it you can still serve a static shell and stream the dynamic parts
Thai
I’m using cache components too but something is up with it
it can be tricky
Thai
I’m ending up with an empty static shell, and streaming is failing
like it messes up ssg
show me the project ic an take a look
Thai
Private repo but basically the root layout.tsx is getting cached, there is a suspense boundary here and it seems to be stuck on suspense
how do you knwo the lay out is gettign cached? what part of it is dynamic?
Thai
Basically it’s an auth boundary and should show loading state while loading
I can tell it’s being cached because of the cache control header
when you use cache compontents you dont use other caches just 'use cache'
becuase you can 'use cache' on components and promises
and 'use cache: private' for things behind auth
Thai
Maybe it’s something with SST / OpenNext?
anythign that is dynamic should also be wrapped in suspense
Thai
export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" {...mantineHtmlProps} suppressHydrationWarning>
      <head>
        <meta charSet="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1, viewport-fit=cover"
        />
        <meta name="mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta
          name="apple-mobile-web-app-status-bar-style"
          content="black-translucent"
        />
        <meta name="apple-mobile-web-app-title" content="Mantis Protect" />
        <meta name="theme-color" content="#339AF0" />
        <link rel="manifest" href="/manifest.json" />
        <Suspense fallback={null}>
          <NonceColorSchemeScript />
        </Suspense>
      </head>
      <body suppressHydrationWarning>
        <ConvexClientProvider>
          <Suspense fallback={<div aria-busy="true" />}>
            <NonceMantineWrapper>
              <AuthErrorBoundary>{children}</AuthErrorBoundary>
            </NonceMantineWrapper>
          </Suspense>
        </ConvexClientProvider>
      </body>
    </html>
  );
}
The suspense within convexclientprovider stays stuck on the fallback
Only when deployed to production, and on NextJS 16.1.x
is somethign dynamic inside the suspense ?
Thai
It works fine on 16.0.10
if not that could cause that
Thai
Yeah there’s dynamic stuff in auth error boundary, but why does it work on 16.0.10 and not 16.1.x?
use suspsnse that high up would cause every page to be dynamic or ppr
like for example the clerk auth provider doesnt count as dynamic so in my app i can still have static pages
return (
        <ClerkProvider>
            <QueryProviders>
                <FCMProvider>
                    <html lang="en">
                        <body
                            className={`${geistSans.variable} ${geistMono.variable} antialiased`}
                            style={{
                                margin: 0,
                                padding: 0,
                                minHeight: '100vh',
                                display: 'flex',
                                flexDirection: 'column',
                            }}
                        >
                            <div
                                style={{
                                    flex: 1,
                                    backgroundImage: 'url(/lawn4.png)',
                                    backgroundSize: 'cover',
                                    backgroundAttachment: 'fixed',
                                    backgroundRepeat: 'no-repeat',
                                }}
                            >
                                <Header>
                                    <HeaderHeader />
                                </Header>
                                <PageContainer>
                                    {children}
                                    <Analytics />
                                </PageContainer>
                            </div>
                            <Footer />
                            <Toaster />
                        </body>
                    </html>
                </FCMProvider>
            </QueryProviders>
        </ClerkProvider>
    )
}
ad b4 you ask lol Im using tanstack use query but for the mutations, all my data is fetched from the server and im using 'use cache' not tanstack cache
if you use suspense on the children then you cant have any part of any page served in the static shell
Thai
Yea that’s fine, there isn’t really much of a static shell to begin with as everything is depending on the user being authenticated and having the proper permissions
if you could fine soem way to do soem of that stuff on the client you could server more content
Thai
Auth / permission checks on the client introduces the risk of tampering from the client
lol sorry im a cached components fan boy
the client can check with the server
thats fine that woudltn make it dynamic
its when you check the auth on the server it becomes dynamic
lol that sounds stupid
its all about where the fetch is called from not where the data is comming from
your always going to check the server for auth one way or another, the difference is, is the client asking the server or is the server asking the server
Thai
Never trust the client
If you do your auth check on the server, and handle the logic conditionally from the server, the client can’t do any funny business in the browser to circumvent it
No but the client can ask the server is verified and share not sensitive information
Thai
Yes true, for example if you have an API route that only returns sensitive data if the user passes an authentication check from the server
Yes, but by definition that API wrote would only return the sensitive data to somebody with the proper credentials
Because it would check the header incoming from the client, verify on the server and then send the information back to the client
Thai
I personally hardcode my API keys on the client bundle /s
?
Thai
I’m kidding lol
Lol
My face
Like clerks hooks for user data arnt leaking data
Only clients with the right cookies can return the data
Gray-tailed TattlerOP
changed that server component to a client component and now i'm getting ssg routes
Answer
Rad glad I could help