> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Streaming Server-Side Rendering

Streaming rendering is an advanced rendering method that progressively returns content during the page rendering process, significantly improving user experience.

In traditional SSR rendering, the page is rendered all at once, requiring all data to be loaded before returning the complete HTML. In streaming rendering, the page is rendered progressively, allowing content to be returned as it renders, so users can see initial content faster.

:::info Default Mode
**Streaming SSR is the default rendering mode for Modern.js SSR**. When you enable SSR, streaming rendering is available without additional configuration.

If you need to switch to traditional SSR mode (waiting for all data to load before returning at once), you can configure `server.ssr.mode` to `'string'`. For detailed information, refer to the [Server-Side Rendering (SSR)](/guides/basic-features/render/ssr.md) documentation.
:::

Compared to traditional SSR rendering:

- **Faster Perceived Speed**: Streaming rendering can progressively display content, quickly rendering the home page.
- **Enhanced User Experience**: Users can see page content faster and interact without waiting for the entire page to render.
- **Better Performance Control**: Developers can better control the loading priority and order, optimizing performance and user experience.
- **Better Adaptability**: Streaming rendering adapts better to various network speeds and device performance, ensuring good performance across different environments.

## Enabling Streaming Rendering

Modern.js supports React 18+ streaming rendering. When you enable SSR, streaming rendering is enabled by default:

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

export default defineConfig({
  server: {
    ssr: true, // Streaming rendering enabled by default
  },
});
```

If you need to explicitly specify streaming rendering mode, you can configure:

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

export default defineConfig({
  server: {
    ssr: {
      mode: 'stream', // Explicitly specify streaming rendering mode
    },
  },
});
```

Modern.js streaming rendering is based on React Router and involves several key APIs:

- [`Await`](https://reactrouter.com/en/main/components/await): Used to render the asynchronous data returned by the Data Loader.
- [`useAsyncValue`](https://reactrouter.com/en/main/hooks/use-async-value): Used to fetch data from the nearest parent `Await` component.

## Fetching Data

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

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

export interface Data {
  data: User;
}

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

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

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

Here, `user` is a Promise object representing asynchronously fetched data, processed using `defer`. Notice that `defer` must receive an object parameter; a direct Promise cannot be passed.

Additionally, `defer` can receive both asynchronous and synchronous data. In the example below, short-duration requests are returned using object data, while longer-duration requests are returned using a Promise:

```ts title="user/[id]/page.data.ts"
export const loader = async ({ params }: LoaderFunctionArgs) => {
  const userId = params.id;

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

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

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

This way, the application can prioritize displaying partially available content without waiting for the most time-consuming data requests.

## Rendering Data

To render the asynchronous data returned by the Data Loader, use the `Await` component. For example:

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

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;
```

The `Await` component needs to be wrapped inside a `Suspense` component. The `resolve` prop of `Await` should be the asynchronously fetched data from the Data Loader. When the data is fetched, it will be rendered using the [Render Props](https://zh-hans.react.dev/reference/react/cloneElement#passing-data-with-a-render-prop) pattern. During data fetching, the content set by the `fallback` prop of `Suspense` is displayed.

:::warning Warning
When importing types from the `page.data.ts` file, use `import type` to ensure only type information is imported, preventing Data Loader code from being bundled into the frontend.
:::

In the component, you can also fetch asynchronous data returned by the Data Loader using `useAsyncValue`. For example:

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

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` prop of the `Await` component handles errors in Data Loader or sub-component rendering. For example, intentionally throwing 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, fetch the error using `useAsyncError` and set a component to render the error message for the `errorElement` prop 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>;
}
```

## Controlling When to Wait for Full HTML

Streaming improves perceived speed, but in some cases (SEO crawlers, A/B buckets, compliance pages) you may want to wait for all content before sending the response.

Modern.js decides the streaming mode with this priority:

1. Request header `x-should-stream-all` (set per-request in middleware).
2. Env `MODERN_JS_STREAM_TO_STRING` (forces full HTML).
3. [isbot](https://www.npmjs.com/package/isbot) check on `user-agent` (bots get full HTML).
4. Default: stream shell first.

Set the header in your middleware to choose the behavior dynamically:

```ts title="middleware example"
export const middleware = async (ctx, next) => {
  const ua = ctx.req.header('user-agent') || '';
  const shouldWaitAll = /Lighthouse|Googlebot/i.test(ua) || ctx.req.path === '/marketing';

  // Write a boolean string: true -> onAllReady, false -> onShellReady
  ctx.req.headers.set('x-should-stream-all', String(shouldWaitAll));

  await next();
};
```


## Related Documentation

- [Rendering Mode Overview](/guides/basic-features/render/overview.md)
- [Server-Side Rendering (SSR)](/guides/basic-features/render/ssr.md)
- [Rendering Cache](/guides/basic-features/render/ssr-cache.md)
- [React Server Components (RSC)](/guides/basic-features/render/rsc.md) - Use with Streaming SSR
- [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37) - React 18 Architecture Overview
