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

# Server-Side Rendering

`@module-federation/modern-js-v3` offers powerful capabilities, enabling developers to easily combine Module Federation with server-side rendering (SSR) in Modern.js applications.

## Enable SSR

Using the application created in [Using Module Federation](/guides/topic-detail/module-federation/usage.md) as an example, you only need to add the `server.ssr` configuration to both the producer and the consumer:

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

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

For better performance, we only support using this capability combination in Streaming SSR scenarios.

:::warning
Application-level modules (modules using `createBridgeComponent` and `createRemoteAppComponent`) use a **staged stability contract** for SSR.

Enable it explicitly in both host and remote apps:

```ts title="modern.config.ts"
export default defineConfig({
  server: {
    ssr: {
      mode: 'stream',
      moduleFederationAppSSR: true,
    },
  },
});
```

For production rollout, keep fallback boundaries for remote load failures and monitor fallback markers in logs/telemetry.

`moduleFederationAppSSR` enables SSR-compatible Module Federation bundling and runtime behavior. It does not guarantee that every remote component renders its final HTML in the host server response. Some router/runtime combinations, including the TanStack Router fixture, intentionally use typed SSR fallback metadata in the shell and let client hydration replace remote placeholders.
:::

## Node SSR Bundle Compatibility

When Module Federation is enabled together with `server.ssr`, use the framework
SSR and Module Federation configuration surfaces instead of forcing bundler
output by hand. Module Federation application bundles are not a safe place to
opt into `output.module`; keep MF remotes and hosts on the framework-generated
MF output mode unless your app has a verified custom runtime contract.

UltraModern generated workspaces keep the root workspace and shared packages as
ESM, but generated MF app packages intentionally do not add app-level
`"type": "module"`. Older repos should migrate by upgrading the framework
cohort and running the generated workspace validation, not by adding app-level
package mode changes, custom navigation/server wrappers, or Module Federation
output shims.

For Cloudflare SSR deploys, the outer Worker entry is an ESM module worker while
copied Modern.js SSR worker bundles stay in a nested CommonJS package scope so
the module Worker can import the emitted worker bundles consistently.

## Battle-Tested Rollout Checklist

Before enabling this for broad production traffic, verify the following scenarios:

1. Host and all remotes use `server.ssr.mode: 'stream'` and the same MF SSR contract flag.
2. Happy-path SSR works in `dev`, `build`, and `serve` modes, either as server-rendered remote HTML or as an explicit typed fallback contract.
3. Remote unavailable path is covered by explicit fallback boundaries (no server crash).
4. Data-fetch timeout and contract-error cases return deterministic fallback UI.
5. Trace and telemetry markers are present across host/remote boundaries.
6. Canary rollout with progressive traffic increase has clear rollback criteria.

## Example Rollout Scenarios

1. Baseline enablement (host + remote):

- Configure both sides with `server.ssr.mode: 'stream'` and `moduleFederationAppSSR: true`.
- Verify SSR HTML contains either remote shell markers or an explicit typed fallback contract that names the client-hydrated remotes.

2. Remote unavailable fallback:

- Keep host flag enabled, stop one remote service, request a remote route.
- Verify response is still `200`, fallback boundary renders, and server does not crash.

3. Timeout and contract-error drill:

- Inject a delayed remote/data response and a malformed payload in CI.
- Verify deterministic fallback UI and matching telemetry reason codes.

## Data Fetching

:::tip
This capability is still in staged rollout. Keep fallback boundaries and run the checklist above in CI before widening traffic.
:::

Module Federation now supports [data fetching](https://module-federation.io/zh/guide/basic/data-fetch/index.html#%E7%AE%80%E4%BB%8B) capabilities. Each producer file can have a corresponding data fetching file, with the file name format of `[name].data.ts`.

In Modern.js, data fetching can be used with SSR. Using the example in the previous chapter, create a data fetching file:

```ts title="src/components/Button.data.ts"
import type { DataFetchParams } from '@module-federation/modern-js-v3/react';

export type Data = {
  data: string;
};

export const fetchData = async (params: DataFetchParams): Promise<Data> => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        data: `data: ${new Date()}`,
      });
    }, 1000);
  });
};
```

In Button, we get the data from the `Props`:

```ts title="src/components/Button.tsx"
import React from 'react';
import type { Data } from './Button.data';

export const Button = (props: { mfData: Data }) => {
  const { mfData } = props;
  return (
    <button type="button" className="test">
      Remote Button {mfData?.data}
    </button>
  );
};
```

## Consuming Components

Consumers must use [`createLazyComponent`](https://module-federation.io/practice/bridge/react-bridge/load-component.html#what-is-createlazycomponent) to load remote components and specify the export as the component name.

```tsx title="src/routes/page.tsx"
import type { JSX } from 'react';
import { getInstance } from '@module-federation/modern-js-v3/runtime';
import {
  ERROR_TYPE,
  lazyLoadComponentPlugin,
} from '@module-federation/modern-js-v3/react';

const instance = getInstance();
instance!.registerPlugins([lazyLoadComponentPlugin()]);

const Button = instance!.createLazyComponent({
  loader: () => {
    return import('remote/Button');
  },
  loading: 'loading...',
  export: 'Button', // Configure this as the export name of the remote component
  fallback: ({ error, errorType, dataFetchMapKey }) => {
    console.error(error);
    if (errorType === ERROR_TYPE.LOAD_REMOTE) {
      return <div>load remote failed</div>;
    }
    if (errorType === ERROR_TYPE.DATA_FETCH) {
      return (
        <div>
          data fetch failed, the dataFetchMapKey key is: {dataFetchMapKey}
        </div>
      );
    }
    return <div>error type is unknown</div>;
  },
});

const Index = (): JSX.Element => {
  return (
    <div>
      <h1>Basic usage with data fetch</h1>
      <Button />
    </div>
  );
};

export default Index;
```
