Reputation: 1651
the following question about the usage of next.js and react to achieve SSR build on top of each other, so I thought I'd write that into a single post. My main question is the third one, but I feel I need to first understand the first two questions in order to get there. So here we go:
1. Am I right that the whole page is always reexecuted from scratch after the client has received it?
Consider this next.js page component:
const Page = () => {
const [state, setState] = useState(getState());
function getState() {
console.log("compute initial state");
return 1;
}
return <>{state}</>;
};
As far as I can tell, getState()
is executed both on the server and on the client. If I'd want to exectue that computation only on the server I'd have to do it via getInitialProps()
resp. getServersideProps()
, right?
2. What value does the prerendered document have for the client if it will be immediately thrown away?
Taking the first question a step further, why is the prerendered document event handed to the client if it will be recalculated from scratch anyway. What advantages does it have for the client to get that initial document? Ok if the case the client can't execute js at all they at least have something. But is that all?
3. Does this mean I unnecessarily have to "double render" some components on the client side?
Let's say parts of my code depend on window
and can't be executed on the server. As explained in different articles I found, it can lead to problems (and react warnings) if I rely on checks like typeof window === "undefined"
in my code. Instead I think the better way is to execute those functionalities after the first render with a useEffect
:
const Page = () => {
const [value, setValue] = useState();
// Effect will be executed after the first render, e.g. never on the server
useEffect(() => {
const value = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(value)
}, []);
return (
<>
{!value && <h1>value pending ...</h1>}
{value && <h1>{value}</h1>}
</>
);
};
Now, with this pattern the app is fine from an SSR perspective. BUT I am introducing additional effort that has do be done on the client side: Even though window
is defined on first render, it can only be used on second render.
For this smaller example this doesn't play a big role, but in a larger app, a flickering may be visible because an unnecessary first render is updated some milliseconds later. Furthermore the code becomes harder to read and more error-prone.
The solution I would want here is related to the first question above: Can I somehow avoid the app from starting from scratch but directly start with the second render? Is there some kind of pattern for this that I've missed so far?
Note: Of course I could rewrite the component to simply check if window
is defined:
return (
<>
<h1>{window ? window.innerWidth : "value pending ..."}</h1>
</>
);
However this will cause a react warning and cause other problems as described in the article i have liked above. Also see the react docs:
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them.
Thank you a lot for your help!
Upvotes: 0
Views: 1120
Reputation: 5840
Am I right that the whole page is always reexecuted from scratch after the client has received it?
Yes and no. The initial html is built on the server and sent to the client as html, and then react is hydrated so that your page becomes interactive. As an example, consider this "page":
export default function MyPage() {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
setGreeting("Goodbye")
}, [])
return (<p>{greeting}</p>)
}
When the page is rendered on the server, it will render as html and send that html to the client. So if you inspect the source code of the page, you'll see:
<p>Hello</p>
Then, on the client, react is hydrated and useEffect
runs, so in the browser you'd see a paragraph with the word "Goodbye".
This is true whether you're using SSR (the html is created on demand on the server) or SSG (the html is created at build time into a static html page).
What value does the prerendered document have for the client if it will be immediately thrown away?
The whole document is not thrown away if you see my first point. The value in SSR is if you want the initial greeting text to be something other than "Hello". For example, if you wanted your server to parse an auth token, get the user profile, and seed greeting
with "Hello Jim" on page load, you'd prefer SSR. If you don't have any serverside processing prior to sending the html to the client, you could choose SSG.
Consider these two "pages":
// Using SSR
export default function MyPage({customerName}) {
return (<p>{customerName}</p>)
}
// Using SSG
export default function MyPage({customerName}) {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
// Call server to get the customer's name
const name = myApi.get('/name')
setGreeting(`Hello ${name}`)
}, [])
return (<p>{greeting}</p>)
}
In the first example, the server renders the p
tag with the customer name (coming from some process on the server) so the html source code will include that customer's name. Nothing is thrown out here.
In the second example, the site is built as html and that source code's p
tag says "Hello". When the page is visited, useEffect
runs and the p
tag is updated when your api responds. So the user will see "Hello" for x microseconds and then it will switch to "Hello Jim".
Does this mean I unnecessarily have to "double render" some components on the client side?
No - you control what you render on the server versus on the client. If you seed a component with data on the server and don't change it on the client, it won't rerender.
In your example, yes - you're double rendering. But you may not need to. As you noted, window
doesn't exist on the client. If you absolutely must show your "value pending..." line to your user before you get the window size, then you'll do double rendering - once to populate that string on the server and then once to replace it when react hydrates on the client.
If you don't need to show that pending line and just need to show the value on the client when window
actually exists, you could rewrite it like this:
export default function Page() {
const [value, setValue] = React.useState();
React.useEffect(() => {
const newValue = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(newValue)
}, []);
if(value) {
return <h1>{value}</h1>
}
return null
};
In this case, nothing is rendered on the server because there is no value
on the server. Only when the client is hydrated will useEffect
run, update value
, and render your component one time.
Upvotes: 1