JavaScript Development Space

How to Implement SSR and Client Hydration in Next.js

Data Assembly and Update Mechanism in SSR and Hydration

This article explains the workflow of server-side rendering (SSR) and client-side hydration in Next.js. It covers the process of server-side data injection, client-side hydration, and mechanisms to ensure timely updates to data (e.g., self.__next_f). Additionally, we discuss handling scenarios where page.js is loaded before the data.

1. Workflow of Server-Side Rendering (SSR)

a) Server Obtains Data

In SSR frameworks like Next.js, data required by the page is fetched server-side, typically using getServerSideProps or getStaticProps.

Example:

jsx
1 export async function getServerSideProps() {
2 const res = await fetch('https://api.example.com/data');
3 const data = await res.json();
4 return { props: { data } };
5 }

b) Injecting Data into HTML

Once data is fetched, it is injected into the HTML, commonly using:

  • Script tags: Serialize the data into JSON and embed it.
  • Global variables: Assign data to global variables like self.__next_f.

c) Send HTML to the Client

The HTML, containing the data, is sent to the client.

d) Client Hydration

On the client side, React and the page JavaScript extract data injected by the server and "activate" the static HTML using ReactDOM.hydrate.

2. Implementation Details

a) Server-Side Data Injection

Data is serialized into JSON and embedded in an HTML <script> tag.

Example:

jsx
1 import ReactDOMServer from 'react-dom/server';
2
3 function App({ data }) {
4 return <div>{data.message}</div>;
5 }
6
7 export async function getServerSideProps() {
8 const data = { message: 'Hello, world!' };
9 return { props: { data } };
10 }
11
12 export function renderToStringWithData(App, props) {
13 const html = ReactDOMServer.renderToString(<App {...props} />);
14 const dataScript = `<script>self.__next_f = ${JSON.stringify(props)};</script>`;
15 return `
16 <!DOCTYPE html>
17 <html>
18 <head><title>SSR Example</title></head>
19 <body>
20 <div id="root">${html}</div>
21 ${dataScript}
22 <script src="/client.js"></script>
23 </body>
24 </html>
25 `;
26 }

b) Client Hydration

The client extracts data from self.__next_f and activates the page.

Example:

jsx
1 import React from 'react';
2 import ReactDOM from 'react-dom';
3 import App from './App';
4
5 const data = self.__next_f;
6 ReactDOM.hydrate(<App {...data} />, document.getElementById('root'));

3. Ensuring Timely Updates to self.__next_f

a) Using Proxy to Monitor Changes

A Proxy object can track changes to self.__next_f and trigger hydration when data updates.

Example:

jsx
1 self.__next_f = self.__next_f || [];
2
3 self.__next_f = new Proxy(self.__next_f, {
4 set(target, prop, value) {
5 const result = Reflect.set(target, prop, value);
6 if (prop === 'length' && value > 0) {
7 console.log('Data updated:', target);
8 hydrateApp(target);
9 }
10 return result;
11 }
12 });
13
14 function hydrateApp(data) {
15 const parsedData = JSON.parse(data[0][1]);
16 ReactDOM.hydrate(<App data={parsedData} />, document.getElementById('root'));
17 }

b) Monitoring Data in page.js

The script waits for data availability before proceeding.

Example:

jsx
1 function waitForData() {
2 return new Promise((resolve) => {
3 if (self.__next_f && self.__next_f.length > 0) {
4 resolve();
5 } else {
6 const observer = new MutationObserver(() => {
7 if (self.__next_f && self.__next_f.length > 0) {
8 observer.disconnect();
9 resolve();
10 }
11 });
12 observer.observe(document.body, { childList: true, subtree: true });
13 }
14 });
15 }
16
17 waitForData().then(() => {
18 const data = JSON.parse(self.__next_f[0][1]);
19 ReactDOM.hydrate(<App data={data} />, document.getElementById('root'));
20 });

4. Handling page.js Loading Before Data

a) Initialize self.__next_f

Add an initialization script in the HTML header:

html
1 <script>
2 self.__next_f = self.__next_f || [];
3 </script>

b) Embed Data Post-HTML

Embed data after the HTML:

html
1 <script>
2 self.__next_f.push([1, "{\"name\":\"The Octocat\"}"]);
3 </script>

Conclusion

By implementing these strategies, you can:

  • Ensure server-side data is correctly injected into HTML.
  • Facilitate timely client-side hydration.
  • Handle cases where page.js loads before data.

These practices enhance the reliability and efficiency of SSR and hydration in Next.js.

JavaScript Development Space

© 2025 JavaScript Development Space - Master JS and NodeJS. All rights reserved.