SSR Without Hydration
Unanswered
Slate-throated Redstart posted this in #help-forum
Slate-throated RedstartOP
Hey all. Is there a way to do SSR (dynamic route) while disabling hydration? I am looking to only render a route on the server without any shipped JavaScript. Is this possible? Also curious if this is equally possible in the
Also curious if this is possible for static (SSG) routes.
The reason for this is we have to support a browser version that is very out of date, and at times it will not be able to render a page due to missing modern JavaScript features. If there's a way to get around that via some Babel config, then that would also work, but simply shipping no JavaScript to the client would be preferable.
pages router as we've yet to migrate to the app router.Also curious if this is possible for static (SSG) routes.
The reason for this is we have to support a browser version that is very out of date, and at times it will not be able to render a page due to missing modern JavaScript features. If there's a way to get around that via some Babel config, then that would also work, but simply shipping no JavaScript to the client would be preferable.
94 Replies
This is the whole point of React Server Components with the App Router
Static vs dynamic is more about when you do the render server-side, so not really related between having a client render with hydration later on
so yes RSC are totally compatible with static rendering
In the pages router, if JS is disabled, I think you should still see the prerendered HTML
in the sense that it may ship some JS code that will simply never be run
you may give it a try
anyway you can use the App Router just for this page
for instance detect old browser and do an URL rewrite (like a redirection but user doesn't see the URL change) towards "/no-js/your-page" for instance
matching "/app/no-js/page.jsx"
Slate-throated RedstartOP
I was looking for something along the lines of this from SvelteKit:
https://kit.svelte.dev/docs/page-options#csr
https://kit.svelte.dev/docs/page-options#csr
You can disable any CSR explicitly via some pragma/option, and I was wondering if NextJS had something similar
I've been attempting to get the app router working, but for some reason I'm getting an error with
emotion adding context to server components (our pages router uses MUI), even though no context is set in the app router. I was under the impression that the pages and app router could co-exist, but apparently that is not fully accurate as they apparently can affect each other?Svelte approach is a bit similar to how Astro treats React component: as a default it removes the JS from them, unless you use some client directives to mark them as being interative and needing csr
in Next you need to split your code between RSC (~ . Astro components) and client components
Slate-throated RedstartOP
Alright. So now this discussion is going to transition to app dir migration I guess.
I decided to give it a shot and made a simple app dir with just a single page rendering a
I decided to give it a shot and made a simple app dir with just a single page rendering a
div and a root layout that simply added the html and body tags. When I run it, I get an issue of ”cannot set context in a server component", and it points to Emotion being the cause. Is the pages dir somehow adding context to the app dir?I'll see if I can make a sample repo
they have been working actively on RSC support recently but it's difficult to mimick the concept of client concept server-side
you need to play around intelligently with React cache and it's really tricky
Josh Comeau has been exploring Linaria to replace Styled component API
it's far from perfect though according to his feedback
Normally no app dir and pages dir have different layouts
you might just have reused some of your components that use emotion somehow, I guess
@Eric Burel Normally no app dir and pages dir have different layouts
Slate-throated RedstartOP
That's why I'm so puzzled
Because in my situation, nothing is declared to be rendered in the app dir but I'm still getting context issues when navigating to the app dir page.
Essentially it's:
// app/layout.jsx
export function RootLayout({children}) {
return (
<html><body>{children}</body></html>
)
}
// app/child/page.jsx
export const Page = () => <div/>And when using that alongside the
pages router, when navigating to /child I get a "cannot set context in server component" errorhmm right do you have a callstack for this error
maybe _document or _app are still run
Slate-throated RedstartOP
Figured out the issue. It was
"jsxImportSource": "@emotion/react", in tsconfigSlate-throated RedstartOP
@Eric Burel so after fixing the issue, I went back and rendered everything entirely in the app router, without using
"use client";. NextJS still ships a bunch of javascript to the client. Is there any way to do entirely SSR without any CSR using NextJS?@Slate-throated Redstart Hey all. Is there a way to do SSR (dynamic route) while disabling hydration? I am looking to only render a route on the server without any shipped JavaScript. Is this possible? Also curious if this is equally possible in the `pages` router as we've yet to migrate to the `app` router.
Also curious if this is possible for static (SSG) routes.
The reason for this is we have to support a browser version that is very out of date, and at times it will not be able to render a page due to missing modern JavaScript features. If there's a way to get around that via some Babel config, then that would also work, but simply shipping no JavaScript to the client would be preferable.
You should be able to do it if you carefully avoid any client-side features. You wont bea able to avoid hydration but you can implement progressive enhancement to your app so that it can still be used for browser without modern javascript
Slate-throated RedstartOP
@ᴉuɐpɹɐɐ So there's absolutely no way to do SSR without hydration in NextJS?
@Slate-throated Redstart <@194128415954173952> So there's absolutely no way to do SSR without hydration in NextJS?
if you dont have client components and suspense in your code, then it should be usable without javascript
Slate-throated RedstartOP
@ᴉuɐpɹɐɐ check here: https://stackblitz.com/edit/stackblitz-starters-apkclb?description=The%20React%20framework%20for%20production&file=app%2Fpage.tsx&title=Next.js%20Starter
Both
Both
layout.tsx and page.tsx do not use the "use client"; pragma, but yet there are still script tags that are loaded below the <main>. I assume that JS would be called by the framework on the client side?@Slate-throated Redstart <@194128415954173952> check here: https://stackblitz.com/edit/stackblitz-starters-apkclb?description=The%20React%20framework%20for%20production&file=app%2Fpage.tsx&title=Next.js%20Starter
Both `layout.tsx` and `page.tsx` do not use the `"use client";` pragma, but yet there are still script tags that are loaded below the `<main>`. I assume that JS would be called by the framework on the client side?
have you tried
npm run build , npm run start and actually run the website without javascriptSlate-throated RedstartOP
Ahhhh, right it must be the HMR
Slate-throated RedstartOP
Nope, that was not it.
// app/layout.tsx
import { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html>
<head>
<title>Server-Side Rendered Page</title>
<meta
name="description"
content="A page rendered entirely on the server."
/>
</head>
<body>{children}</body>
</html>
);
}
// app/page.tsx
export const metadata = {
title: "Server-Side Rendered Page",
description: "A page rendered entirely on the server.",
};
export default async function Page() {
const res = await fetch("https://catfact.ninja/fact");
const data: { fact: string; length: number } = await res.json();
return (
<div>
<h1>Server-Side Rendered Page</h1>
<p>{data.fact}</p>
</div>
);
}looks good to me
Slate-throated RedstartOP
what seems to be the problem
Slate-throated RedstartOP
Essentially I am trying to eliminate any loaded JS to the browser, and only display HTML
Even if the JS is in a script tag
Slate-throated RedstartOP
Very, very, VERY old browser needs to be supported
will it break if it see a single <script> tag?
Slate-throated RedstartOP
Chromium 68, to be exact
It's less that and more that we've been burned by random node_modules having JS that it couldn't handle
Things like:
-
- Optional chaining
-
globalThis (had to have a polyfill to deal with it)- Optional chaining
so it doesnt break if it sees a single <script> tag?
Slate-throated RedstartOP
It breaks if it sees any optional chaining in the browser, which means we cannot update to
date-fns: ^3.0.0As an example
idk man try running localhost:3000 in your chromium 68 browser and see if it breaks
Slate-throated RedstartOP
The idea was just to eliminate any shipped JS to this browser and simply render on the server and display the resulting HTML
This way we would never have to worry about a new node package updating and adding back in a breaking syntax
i dont think thats relevant.
if it doesnt break in this current version then just dont upgrade it
your webpage can still run without javascript then possibly tell user to run it without javascript
if it doesnt break in this current version then just dont upgrade it
your webpage can still run without javascript then possibly tell user to run it without javascript
if your page break, might want to choose another framework
Slate-throated RedstartOP
It's a framework that has this browser, not a user. And it's a very... specialized framework
It renders the page in a desktop application
But it requires dynamic data
then try the default next.js template, build it, run it in that specific specialized framework that has that specific browser
what you import in your code base is irrelevant to the final product since those imported libs are only present during rendering and not runtime
thats the whole point of static site generation
thats the whole point of static site generation
Slate-throated RedstartOP
Currently we do that, and if we see regressions then we have to hunt down the offending node package. Once it was a dependency of a dependency of a dependency...
no thats irrelevant
if something break, its something wrong with Next.js, not with what you imported.
@ᴉuɐpɹɐɐ what you import in your code base is irrelevant to the final product since those imported libs are only present during rendering and not runtime
thats the whole point of static site generation
Slate-throated RedstartOP
We're not doing SSG. We are doing SSR
fortunately, also the point of server side rendering
Slate-throated RedstartOP
Yes, but it still hydrates on the client and runs JS there as well
have you tried it?
Slate-throated RedstartOP
With app dir? No. Only pages dir at present.
try with app dir
Slate-throated RedstartOP
Only just got app dir to work today alongside our pages dir
because the entirety of pages dir is client components
@ᴉuɐpɹɐɐ because the entirety of pages dir is client components
Slate-throated RedstartOP
Even if you use
getServerSideProps?those only hydrate the initial data, pass it to the client component for it to render with initial data
it doesnt render the HTML dom element
let me know if you have tried ssr with app dir in your specific browser of a specialized framework
Slate-throated RedstartOP
Here's hoping this is the answer 🤞
@Slate-throated Redstart <@769111741098622976> so after fixing the issue, I went back and rendered everything entirely in the app router, without using `"use client";`. NextJS still ships a bunch of javascript to the client. Is there any way to do entirely SSR without any CSR using NextJS?
Depends on the specific JS you are talking about, for instance if you use streaming via defining a "loading.tsx", you'll notice the HTML code contains some JS to control display, there are few things like that, Next.js uses an hybrid approach mixing client side and server side patterns so it may feel kind hard to actually get 0 js
but coming from client-side react there has been appreciable projects yet and your app should most probably display correctly with JS disabled
which is what you might want to focus on, to discuss details you'll have to reach out React/Next core contribs
I've right something exactly yesterday about self next push
it's a hard read, I've read it like twice yesterday and still didn't figure everything
but at least I've noticed that not sending translation tokens and using RSCs instead transforms my 37kb HTML file into a 5kb HTML file
Regarding polyfill I am battling for supports of endware
scripts should be inserted after the HTML is generated
realistically this will never be implemented but you might want to upvote still, who knows
You should probably escalate your use case through github issues or twitter
"I think when folks hear "Server Components don't require hydration" they think there will be nothing but HTML sent from the server. But really we need the full "bundle" which represents the state of the application as it was SSR'd and this necessarily includes representation of the tags in a format that create the right component tree on the client. In a client only React application the information about what tags will exist is encoded in the static bundle assets but you have to recompute it on the client. With RSC it is encoded in the initial data sent from the server and you don't need to recompute it but it is no longer able to be static (since it very likely depended on something like data fetching to produce the result)"
Slate-throated RedstartOP
Alright, looks like instead I'll be looking at
ReactDOM.renderToString(), because we've had a few issues with random node_modules running with code that the app browser couldn't handle (such as date-fns v3 using optional chaining). That probably will be the best I can get. I could always put that in the route.tsx file under GET, I think?@Slate-throated Redstart Alright, looks like instead I'll be looking at `ReactDOM.renderToString()`, because we've had a few issues with random node_modules running with code that the app browser couldn't handle (such as `date-fns` v3 using optional chaining). That probably will be the best I can get. I could always put that in the `route.tsx` file under GET, I think?
That's a pretty good idea actually I didn't think of it earlier but yes
I didn't test if you can use jsx in them but I think yes, I used to generate email with some jsx in the pages router
I didn't test if you can use jsx in them but I think yes, I used to generate email with some jsx in the pages router