---
title: Server-side rendering
sidebar_position: 3
---
# Server-side rendering

In Modern.js, SSR also works out of the box. Developers do not need to write complex server level logic for SSR, nor do they need to care about the operation and maintenance of SSR, or create services. Modern.js have a comprehensive SSR degradation strategy to ensure that pages can run safely.

Enabling SSR is very easy, just set ['server.ssr'](/configure/app/server/ssr) to `true`:

```ts title="modern.config.ts"
import { defineConfig } from '@modern-js/app-tools';

export default defineConfig({
  "server": {
    "ssr": true,
  },
})
```

## SSR Data Fetch

Modern.js provides Data Loader, which is convenient for developers to fetch data under SSR and CSR. Each routing module, such as `layout.tsx` and `page.tsx`, can define its own Data Loader:

```ts title="src/routes/page.loader.ts"
export default () => {
  return {
    message: 'Hello World',
  };
};
```

in the component, the data returned by the `loader` function can be get data through the Hooks API:

```tsx
export default () => {
  const data = useLoaderData();
  return <div>{data.message}</div>;
};
```

Modern.js break the traditional SSR development model and provide users with a user-friendly SSR development experience.

And it provides elegant degradation processing. Once the SSR request fails, it will automatically downgrade and restart the request on the browser side.

However, developers still need to pay attention to the fallback of data, such as `null` values or data returns that do not as expect. Avoid React rendering errors or messy rendering results when SSR.

:::info
1. When you request the page on client-side page transitions, Modern.js sends an API request to the server, which runs Data Loader function.

2. When using Data Loader, data fetching happens before rendering, Modern.js still supports fetching data when the component is rendered. See [Data Fetch](/guides/basic-features/data-fetch).

:::

## Keep Rendering Consistent

In some businesses, it is usually necessary to display different UI displays according to the current operating container environment characteristics, such as [UA](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) information.

If the processing is not careful enough, the rendering results may do not meet the expectations at this time.

Here is an example to show the problem when SSR and CSR rendering are inconsistent, add the following code to the component:

```tsx
{
  typeof window !== 'undefined' ? <div>browser content</div> : null;
}
```

After starting the app, visit the page and will find that the browser console throws a warning message:

```sh
Warning: Expected server HTML to contain a matching <div> in <div>.
```

This is caused by the inconsistency between the rendering result and the SSR rendering result when React executes the hydrate logic on the client side. Although the page performs normally, in complex applications, it is likely to cause problems such as DOM hierarchy confusion and style confusion.

:::info
For hydrate logic, please refer to [here](https://reactjs.org/docs/react-dom.html#hydrate).

:::

Applications need to maintain the consistency of SSR and CSR rendering results. If there are inconsistencies, it means that this part of the content does not need to be rendered in SSR.

Modern.js provide [`<NoSSR>`](/apis/app/runtime/core/use-runtime-context) for such content that does not need to be rendered in SSR:

```ts
import { NoSSR } from '@modern-js/runtime/ssr';
```

Outside of elements that do not require SSR, wrap with a `NoSSR` component:

```tsx
<NoSSR>
  <div>client content</div>
</NoSSR>
```

After modifying the code, refresh page found that the previous Waring disappeared. Open the Network window of the browser developer tool to see that the returned HTML document does not contain the content of the `NoSSR` component package.

:::info
['useRuntimeContext'](/apis/app/runtime/core/use-runtime-context) can get complete request information, which can be used to ensure that the rendering results of SSR and CSR are consistent.

:::

## Concerned Memory Leaks

:::warning
In the SSR, developers need to pay special attention to the problem of memory leaks. Even small memory leaks can affect services..

:::

In SSR, every request triggers the component rendering. So, you need to avoid defining any potentially growing global data, or subscribing to events globally, or creating streams that will not be destroyed.

For example, the following code, when using [redux-observable](https://redux-observable.js.org/), developers used to code like this:

```tsx
import { createEpicMiddleware, combineEpics } from 'redux-observable';

const epicMiddleware = createEpicMiddleware();
const rootEpic = combineEpics();

export default function Test() {
  epicMiddleware.run(rootEpic);
  return <div>Hello Modern.js</div>;
}
```

Create a Middleware instance `epicMiddleware` outside the component and call epicMiddleware.run inside the component.

On the browser side, this code does not cause any problems. But in SSR, the Middleware instance will never be destroyed. Every time the component is rendered and `rootEpic` is called, new event bindings are added internally, causing the entire object to continue to grow larger, which ultimately affects application performance.

Such problems in CSR are not easy to detect, so when switching from CSR to SSR, if you are not sure whether the application has such hidden dangers, you can press the application.

## Crop SSR Data

In order to keep the data requested in the SSR phase, it can be used directly on the browser side, Modern.js inject the data and state that collected during the rendering process into the HTML.

As a result, CSR applications often have a large amount of interface data and the state of the components is not crop. If SSR is used directly, the rendered HTML size may be too large.

At this time, SSR not only cannot bring an improvement in the user experience, but may have the opposite effect.

Therefore, when using SSR, **developers need to do reasonable weight loss for the application**:

1. Pay attention to the first screen, you can only request the data needed for the first screen in SSR, and render the rest on the browser side.
2. Removes the data independent with render from the data returned by the interface.

## Serverless Pre-render

Modern.js provide Serverless Pre-rendering(SPR) to improve SSR performance.

SPR uses pre-rendering and caching to provide the responsive performance of static Web for SSR pages. It allows SSR applications to have the responsiveness and stability of static Web pages, while keeping data dynamically updated.

Using SPR in the Modern.js is very simple, just add the `<PreRender>` component, and the page where the component is located will automatically open SPR.

This mock a component that uses the `useLoaderData` API, and the request in the Data Loader takes 2s.

```jsx
import { useLoaderData } from '@modern-js/runtime/router';

export const loader = async () => {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(null);
    }, 2000);
  });

  return {
    message: 'Hello Modern.js',
  };
};

export default () => {
  const data = useLoaderData();
  return <div>{data?.message}</div>;
};
```

After executing the `dev` command and opening the page, it is obvious that the page needs to wait 2s before returning.

The next is to use the `<PreRender>` component, which can be exported directly from `@modern-js/runtime/ssr `:

```ts
import { PreRender } from '@modern-js/runtime/ssr';
```

Use the `PreRender` component in the routing component and set the parameter `interval` to indicate that the expiration time of the rendering result is 5s:

```tsx
<PreRender interval={5} />
```

After modification, execute `pnpm run build && pnpm run serve` to start the application and open the page.

When open page for the first time, it is no different from the previous rendering, and there is also a 2s delay.

Refresh page, and the page opens instantly, but at this time, the page data has not changed due to the refresh, because the cache has not expired.

Wait 5s, refresh the page again, the data of the page is still unchanged. Refresh the page again The data changes, but the page still responds almost instantaneously.

This is because in the previous request, the SPR has asynchronously obtained the new rendering result in the background, and the page requested this time is the version that has been cached in the server.

It is conceivable that when `interval` is set to 1, users can have the responsive experience of a static page.

:::info
For more detail, see [`<PreRender>`](/apis/app/runtime/ssr/pre-render).

:::

## Treeshaking

When SSR is enabled, Modern.js will use the same entry to build both the SSR Bundle and the CSR Bundle. Therefore, the Web API in the SSR Bundle, or the Node API in the CSR Bundle, can lead to runtime errors.

Web API into a component is usually to do some global listening, or to get browser-related data, such as:

```tsx
document.addEventListener('load', () => {
  console.log('document load');
});
const App = () => {
  return <div>Hello World</div>;
};
export default App;
```

The Node API is introduced in the component file, usually because of the use of Data Loader, for example:

```ts
import fse from 'fs-extra';
export default () => {
  const file = fse.readFileSync('./myfile');
  return {
    ...
  };
};
```

### Use Environment Variables

For the first case, we can directly use Modern.js built-in environment variables `MODERN_TARGET` to remove useless code at build time:

```ts
if (process.env.MODERN_TARGET === 'browser') {
  document.addEventListener('load', () => {
    console.log('document load');
  });
}
```

After packaging in the development environment, SSR and CSR artifacts will be compiled into the following content. Therefore, there will be no more errors due to Web API in the SSR environment:

```ts
// SSR production
if (false) {
}

// CSR production
if (true) {
  document.addEventListener('load', () => {
    console.log('document load');
  });
}
```

:::note
For more information, see [environment variables](/guides/basic-features/env-vars).

:::

### Use File Suffix

However, in the second case, for example, when `fs-extra` is imported into the code, it internally uses the Node API with side effects. If it is directly referenced in the component, it will cause CSR loading errors.

Env vars is not effective in this situation. Modern.js also supports distinguishing SSR Bundle and CSR Bundle packaging files through files with the `.node.` suffix.

For example, the import of `fs-extra` in the code, when it is directly referenced to the component, will cause the CSR to load an error. You can create `.ts` and `.node.ts` files of the same name as a layer of proxy:

```ts title="compat.ts"
export const readFileSync: any = () => {};
```

```ts title="compat.node.ts"
export { readFileSync } from 'fs-extra';
```

use `./compat` directly into the file. At this time, files with the `.node.ts` suffix will be used first in the SSR environment, and files with the `.ts` suffix will be used in the CSR environment.

```ts title="App.tsx"
import { readFileSync } from './compat'

export const loader = () => {
  const file = readFileSync('./myfile');
  return {
    ...
  };
};
```

### Independent File

Both of the above methods can bring some mental burden to developers. In real business scenarios, we found that most of the mixed Node/Web code occurs in data requests.

Therefore, Modern.js developed a [Data Fetch](/guides/basic-features/data-fetch) to separate CSR and SSR code based on [Nested Routing](/guides/basic-features/routes).

We can separate **data request** and **component code** by using independent files. Write component logic in `routes/page.tsx` and data request logic in `routes/page.loader.ts`.

```ts title="routes/page.tsx"
export default Page = () => {
  return <div>Hello World<div>
}
```

```ts title="routes/page.loader.tsx"
import fse from 'fs-extra';
export default () => {
  const file = fse.readFileSync('./myfile');
  return {
    ...
  };
}
```

## Remote Request

When initiating remote requests in SSR, developers sometimes use request tools. Some interfaces need to pass user cookies, which developers can get through the ['useRuntimeContext'](/guides/basic-features/data-fetch#route-loader) API to achieve.

It should be noted, the request header of the HTML request is obtained, which may not be applicable to remote requests, so **must not** pass through all request headers.

In addition, some backend interfaces, or general gateways, will verify according to the information in the request header, and full pass-through is prone to various problems that are difficult to debug. It is recommended that **pass-through on demand**.

Be sure to filter the `host` field if you really need to pass through all request headers.

## Streaming SSR

Modern.js supports streaming rendering in React 18. Opt in it with the following configuration:

```ts title="modern.config.ts"
import { defineConfig } from '@modern-js/app-tools';

export default defineConfig({
  "server": {
    "ssr": {
      "mode": "stream",
    },
  },
})
```

The streaming SSR of Modern.js is implemented based on React Router, and the main APIs involved are:

- [`defer`](https://reactrouter.com/en/main/utils/defer): This utility allows you to defer values returned from loaders by passing promises instead of resolved values.
- [`Await`](https://reactrouter.com/en/main/components/await): Used to render deferred values with automatic error handling.
- [`useAsyncValue`](https://reactrouter.com/en/main/hooks/use-async-value): Returns the resolved data from the nearest `<Await>` ancestor component.


### Return async data

```ts title="page.loader.ts"
import { defer, type LoaderFunctionArgs } from '@modern-js/runtime/router';

interface User {
  name: string;
  age: number;
}

export interface Data {
  data: User;
}

export default ({ params }: LoaderFunctionArgs) => {
  const userId = params.id;

  const user = new Promise<User>(resolve => {
    setTimeout(() => {
      resolve({
        name: `user-${userId}`,
        age: 18,
      });
    }, 200);
  });

  return defer({ data: user });
};

```

`user` is of `Promise` type, which means the data will be obtained asynchronously. Note that `defer` must accept an object type parameter,
therefore, the parameter passed to `defer` is `{data: user}`.

`defer` can also receive asynchronous data and synchronous data at the same time. For example:

```ts title="page.loader.ts"

// skip some codes

export default ({ params }: LoaderFunctionArgs) => {
  const userId = params.id;

  const user = new Promise<User>(resolve => {
    setTimeout(() => {
      resolve({
        name: `user-${userId}`,
        age: 18,
      });
    }, 200);
  });

  const otherData = new Promise<string>(resolve => {
    setTimeout(() => {
      resolve('some sync data');
    }, 200);
  });

  return defer({
    data: user,
    other: await otherData
  });
};

```

`await` is added before `otherData`, so the data is obtained synchronously. It can be passed to `defer` with the data `user` at the same time.


### Render async data

Use the `Await` component to render the data returned asynchronously from the Data Loader. For example:

```tsx title="page.tsx"
import { Await, useLoaderData } from '@modern-js/runtime/router';
import { Suspense } from 'react';
import type { Data } from './page.loader';

const Page = () => {
  const data = useLoaderData() as Data;

  return (
    <div>
      User info:
      <Suspense fallback={<div id="loading">loading user data ...</div>}>
        <Await resolve={data.data}>
          {(user) => {
            return (
              <div id="data">
                name: {user.name}, age: {user.age}
              </div>
            );
          }}
        </Await>
      </Suspense>
    </div>
  );
};

export default Page;
```

`Await` needs to be wrapped inside the `Suspense` component. The `resolve` of `Await` passes in the data acquired asynchronously by the Data Loader. When the data acquisition is completed,
the obtained data is rendered through the [Render Props](https://reactjs.org/docs/render-props.html) mode. When the data acquisition is in pending status, the
content set by the `fallback` property of the `Suspense` component will display.


:::warning Warning
When importing a type from a Data Loader file, you need to use the `import type` syntax to ensure that only type information is imported, which can prevent the Data Loader code from being packaged into the client bundle.

So, here we import like this: `import type { Data } from './page.loader'`;
:::

You can also get the asynchronous data returned by Data Loader through `useAsyncValue`. For example:

```tsx title="page.tsx"
import { useAsyncValue } from '@modern-js/runtime/router';

// skip some codes

const UserInfo = () => {
  const user = useAsyncValue();

  return (
     <div>
      name: {user.name}, age: {user.age}
    </div>
  )
}

const Page = () => {
  const data = useLoaderData() as Data;

  return (
    <div>
      User info:
      <Suspense fallback={<div id="loading">loading user data ...</div>}>
        <Await resolve={data.data}>
          <UserInfo />
        </Await>
      </Suspense>
    </div>
  );
};

export default Page;
```

### Error handling

The `errorElement` property of the `Await` component can be used to handle errors thrown when the Data Loader executes or when a child component renders.
For example, we intentionally throw an error in the Data Loader function:

```ts title="page.loader.ts"
import { defer } from '@modern-js/runtime/router';

export default () => {
  const data = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('error occurs'));
    }, 200);
  });

  return defer({ data });
};
```

Then use `useAsyncError` to get the error, and assign the component used to render the error to the `errorElement` property of the `Await` component:

```tsx title="page.ts"
import { Await, useAsyncError, useLoaderData } from '@modern-js/runtime/router';
import { Suspense } from 'react';

export default function Page() {
  const data = useLoaderData();

  return (
    <div>
      Error page
      <Suspense fallback={<div>loading ...</div>}>
        <Await resolve={data.data} errorElement={<ErrorElement />}>
          {(data: any) => {
            return <div>never displayed</div>;
          }}
        </Await>
      </Suspense>
    </div>
  );
}

function ErrorElement() {
  const error = useAsyncError() as Error;
  return <p>Something went wrong! {error.message}</p>;
}
```

:::info More

1. [Deferred Data](https://reactrouter.com/en/main/guides/deferred)
2. [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37)

:::
