Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-rspack-builds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@module-federation/nuxt": minor
---

Add client and server Module Federation support for Nuxt applications using the Rspack builder, including development and production SSR, dedicated Rspack examples, and an Rslib-based package build.
56 changes: 47 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Nuxt Module Federation

Use Module Federation in Nuxt applications with `@module-federation/nuxt`, built on top of `@module-federation/vite`.
Use Module Federation in Nuxt applications with `@module-federation/nuxt`, using Vite or Rspack.

> [!IMPORTANT]
> `@module-federation/nuxt` is still in beta. Expect API changes while the integration settles. Please report bugs and edge cases in this repository.
Expand All @@ -10,7 +10,7 @@ Use Module Federation in Nuxt applications with `@module-federation/nuxt`, built
- Nuxt module wiring for Module Federation hosts and remotes.
- Convention-based component exposes from `~/components/exposed`.
- Remote Vue components registered in Nuxt for template auto-imports.
- Server-rendered remote components in development and production on writable Node deployments.
- Server-rendered remote components with Vite or Rspack in development and production on writable Node deployments.
- Client and server remote entries plus an MF manifest at the public root.
- `vue` and `vue-router` shared as singletons by default.

Expand Down Expand Up @@ -83,12 +83,14 @@ Remote components render on the Nuxt server by default. Production remotes publi

The default upstream SSR loader writes fetched modules to `node_modules/.ssr-cache` below the server working directory. Use `moduleFederation.ssr: false` for read-only or serverless deployments; see the package deployment contract for details.

Nuxt 4.5 uses Vite 8's Rolldown pipeline. The MF server runner uses its ModuleRunner protocol, so remote components render during `nuxt dev` as well as production. Set `moduleFederation.ssr` to `false` to choose client-only rendering in every environment.
With Vite, the MF server runner uses Vite 8's ModuleRunner protocol. With Rspack, the module publishes a portable server bundle graph and loads it through the same SSR runtime contract. Both builders render remote components during `nuxt dev` and production. Set `moduleFederation.ssr` to `false` to choose client-only rendering in every environment.

## Example applications

- Host: [`apps/host`](apps/host) at `http://localhost:4173`
- Remote: [`apps/remote`](apps/remote) at `http://localhost:4174`
- Vite host: [`apps/host`](apps/host) at `http://localhost:4173`
- Vite remote: [`apps/remote`](apps/remote) at `http://localhost:4174`
- Rspack host: [`apps/host-rspack`](apps/host-rspack) at `http://localhost:4175`
- Rspack remote: [`apps/remote-rspack`](apps/remote-rspack) at `http://localhost:4176`

Run both from the repository root:

Expand All @@ -104,19 +106,55 @@ pnpm dev:remote
pnpm dev:host
```

The ports are fixed because the host's remote URL depends on the remote remaining at `4174`.
The ports are fixed because each host's remote URL depends on its remote remaining at the configured port.

### Rspack

Nuxt's Rspack builder creates its compilers through Rsbuild, then exposes the generated low-level configuration through Nuxt's `rspack:config` hook. The module attaches `@module-federation/enhanced/rspack` there. Browser builds enable `experiments.asyncStartup`; server builds intentionally use synchronous startup for Nuxt SSR.

Install the builder and select it in each application:

```bash
pnpm add -D @nuxt/rspack-builder
```

```ts
export default defineNuxtConfig({
builder: "rspack",
modules: ["@module-federation/nuxt"],
moduleFederation: {
config: {
name: "host",
remotes: {
remote: "remote@https://remote.example.com/mf-manifest.json",
},
},
},
});
```

Rspack remotes publish `remoteEntry.js`, `remoteEntry.ssr.js`, and the manifest at `moduleFederation.base` (`/` by default), with the portable server chunk graph under the configured `app.buildAssetsDir`. Hosts use the server entry before hydration and the browser entry afterward.

Nuxt's Rspack development middleware accepts only same-origin asset requests. The dedicated Rspack host proxies the remote's manifest, entry, and chunks through `localhost:4175`; see [`apps/host-rspack/nuxt.config.ts`](apps/host-rspack/nuxt.config.ts). Deployed applications can use direct remote URLs when their production server permits cross-origin federation assets.

Run the example pair with Rspack:

```bash
pnpm dev:rspack
```

## Build checks

```bash
pnpm typecheck
pnpm build
pnpm build:rspack
pnpm test
pnpm test:e2e
pnpm pack:nuxt
```

For a production smoke test, start both built applications with `pnpm preview`, then open `http://localhost:4173` and confirm the remote cards are present before hydration and remain interactive afterward.
For a production smoke test, start both built applications with `pnpm preview` or `pnpm preview:rspack`, then open the matching host and confirm the remote cards are present before hydration and remain interactive afterward.

## Release flow

Expand All @@ -128,7 +166,7 @@ For a production smoke test, start both built applications with `pnpm preview`,
## Repository layout

- Package: `packages/nuxt`
- Host example: `apps/host`
- Remote example: `apps/remote`
- Vite examples: `apps/host`, `apps/remote`
- Rspack examples: `apps/host-rspack`, `apps/remote-rspack`
- Package reference: `packages/nuxt/README.md`
- Release guide: `docs/RELEASING.md`
11 changes: 11 additions & 0 deletions apps/host-rspack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Rspack host

Nuxt Rspack host at `http://localhost:4175`. It server-renders the remote components, then hydrates them with the browser entry.

The host proxies the remote federation routes through its own origin because Nuxt's Rspack development middleware only serves same-origin requests.

Run the Rspack pair from the repository root:

```bash
pnpm dev:rspack
```
25 changes: 25 additions & 0 deletions apps/host-rspack/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const hostOrigin = "http://localhost:4175";
const remoteOrigin = "http://localhost:4176";
const remoteBase = "/rspack-remote-mf";
const remoteAssets = "/rspack-remote-assets";

export default defineNuxtConfig({
extends: ["../host"],
builder: "rspack",
routeRules: {
[`${remoteBase}/**`]: {
proxy: `${remoteOrigin}${remoteBase}/**`,
},
[`${remoteAssets}/**`]: {
proxy: `${remoteOrigin}${remoteAssets}/**`,
},
},
moduleFederation: {
config: {
name: "hostRspack",
remotes: {
remote: `remote@${hostOrigin}${remoteBase}/mf-manifest.json`,
},
},
},
});
21 changes: 21 additions & 0 deletions apps/host-rspack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "nuxt-host-rspack",
"type": "module",
"scripts": {
"build": "nuxt build",
"predev": "nuxt prepare",
"dev": "nuxt dev --port 4175",
"generate": "nuxt generate",
"preview": "nuxt preview --port 4175",
"typecheck": "nuxt typecheck"
},
"dependencies": {
"@module-federation/nuxt": "workspace:*",
"nuxt": "4.5.1",
"vue": "3.5.40",
"vue-router": "5.2.0"
},
"devDependencies": {
"@nuxt/rspack-builder": "4.5.1"
}
}
3 changes: 3 additions & 0 deletions apps/host-rspack/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}
11 changes: 11 additions & 0 deletions apps/remote-rspack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Rspack remote

Nuxt Rspack remote at `http://localhost:4176`.

Federation assets use `/rspack-remote-mf`; compiled browser chunks and the portable SSR graph use `/rspack-remote-assets`. The Rspack host proxies both paths for same-origin development loading.

Run the Rspack pair from the repository root:

```bash
pnpm dev:rspack
```
20 changes: 20 additions & 0 deletions apps/remote-rspack/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { fileURLToPath } from "node:url";

const exposedDir = fileURLToPath(
new URL("../remote/app/components/exposed", import.meta.url),
);

export default defineNuxtConfig({
extends: ["../remote"],
builder: "rspack",
app: {
buildAssetsDir: "/rspack-remote-assets/",
},
moduleFederation: {
base: "/rspack-remote-mf",
exposedDir,
config: {
name: "remote",
},
},
});
21 changes: 21 additions & 0 deletions apps/remote-rspack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "nuxt-remote-rspack",
"type": "module",
"scripts": {
"build": "nuxt build",
"predev": "nuxt prepare",
"dev": "nuxt dev --port 4176",
"generate": "nuxt generate",
"preview": "nuxt preview --port 4176",
"typecheck": "nuxt typecheck"
},
"dependencies": {
"@module-federation/nuxt": "workspace:*",
"nuxt": "4.5.1",
"vue": "3.5.40",
"vue-router": "5.2.0"
},
"devDependencies": {
"@nuxt/rspack-builder": "4.5.1"
}
}
3 changes: 3 additions & 0 deletions apps/remote-rspack/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}
1 change: 1 addition & 0 deletions apps/remote/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineNuxtConfig({
filename: "remoteEntry.js",
remotes: {},
manifest: true,
moduleParseIdleTimeout: 30,
},
},
vite: {
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
"private": true,
"scripts": {
"build": "turbo run build",
"build:rspack": "turbo run build --filter=nuxt-host-rspack --filter=nuxt-remote-rspack",
"build:package": "pnpm --filter @module-federation/nuxt build",
"preview": "pnpm --filter @module-federation/nuxt build && turbo run preview",
"dev": "pnpm --filter @module-federation/nuxt build && turbo run dev",
"preview": "pnpm --filter @module-federation/nuxt build && turbo run preview --filter=nuxt-host --filter=nuxt-remote",
"preview:rspack": "pnpm --filter @module-federation/nuxt build && turbo run preview --filter=nuxt-host-rspack --filter=nuxt-remote-rspack",
"dev": "pnpm --filter @module-federation/nuxt build && turbo run dev --filter=nuxt-host --filter=nuxt-remote",
"dev:rspack": "pnpm --filter @module-federation/nuxt build && turbo run dev --filter=nuxt-host-rspack --filter=nuxt-remote-rspack",
"dev:rspack:host": "pnpm --filter @module-federation/nuxt build && turbo run dev --filter=nuxt-host-rspack",
"dev:rspack:remote": "pnpm --filter @module-federation/nuxt build && turbo run dev --filter=nuxt-remote-rspack",
"dev:host": "pnpm --filter @module-federation/nuxt build && turbo run dev --filter=nuxt-host",
"dev:remote": "pnpm --filter @module-federation/nuxt build && turbo run dev --filter=nuxt-remote",
"test": "node --test --test-concurrency=1 test/*.test.mjs",
Expand Down
35 changes: 31 additions & 4 deletions packages/nuxt/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# @module-federation/nuxt

Nuxt integration for Module Federation, built on top of `@module-federation/vite`.
Nuxt integration for Module Federation, using `@module-federation/vite` or `@module-federation/enhanced/rspack`.

## Requirements

- Nuxt `>=4.5.1` (Vite 8 with Rolldown)
- Node.js `^22.18.0`, `^24.11.0`, or `>=26.0.0`

Rspack applications also require `@nuxt/rspack-builder` matching the installed Nuxt version.

Production builds support server-rendered remote components on writable Node deployments. The default upstream SSR loader writes fetched modules under `process.cwd()/node_modules/.ssr-cache`; read-only and serverless filesystems are not currently supported for remote SSR.

## Install
Expand Down Expand Up @@ -118,7 +120,32 @@ Only expose names beginning with a letter and containing letters, numbers, under
- The Nuxt server loads `remoteEntry.ssr.js`.
- The remote component's HTML is included in the host response and hydrated in the browser.

Nuxt 4.5 uses Vite 8's Rolldown pipeline. The same federation plugin participates in the client and server environments, so remote components render during both `nuxt dev` and production SSR.
Vite uses its ModuleRunner protocol for the server graph. Rspack emits a separate portable server graph. Both builders render remote components during `nuxt dev` and production SSR.

### Rspack builder

Set `builder: "rspack"` and install `@nuxt/rspack-builder` to use federation through `@module-federation/enhanced/rspack`:

```ts
export default defineNuxtConfig({
builder: "rspack",
modules: ["@module-federation/nuxt"],
moduleFederation: {
config: {
name: "shell",
remotes: {
catalog: "catalog@https://catalog.example.com/mf-manifest.json",
},
},
},
});
```

Nuxt creates Rspack compilers through Rsbuild internally, but its module API exposes the generated configuration through `rspack:config`; the Nuxt module installs the Rspack federation plugin at that hook. Browser builds enable `experiments.asyncStartup`; the server build uses synchronous startup so Nuxt can evaluate its SSR entry normally.

Rspack remotes publish `remoteEntry.js`, `remoteEntry.ssr.js`, and `mf-manifest.json` at `moduleFederation.base` (`/` by default), with the server entry's portable chunk graph under `app.buildAssetsDir`. The manifest includes `ssrRemoteEntry` and a `nuxtSsrBuildHash`, allowing hosts to load the server graph before hydration and detect later deployments.

Nuxt's Rspack development middleware rejects cross-origin asset requests even when CORS headers are present. For cross-port local development, proxy the remote manifest and its configured `app.buildAssetsDir` through the host origin. The repository's `host-rspack` and `remote-rspack` examples provide a complete proxy setup. Production servers that allow cross-origin assets can use direct remote URLs.

Development remote manifests infer their asset origin from the manifest URL. This keeps `remoteEntry.js` and exposed chunks on the remote origin when the host and remote use different ports. An explicit `config.publicPath` still takes precedence.

Expand Down Expand Up @@ -203,9 +230,9 @@ Configure these values under `moduleFederation` in `nuxt.config.ts`.
| `ssr` | `boolean` | `true` | Render consumed remote components on the Nuxt server. Server exposes are still published when `false`. |
| `ssrFetchTimeoutMs` | `number` | `10000` | Maximum time for each SSR remote network request; `0` disables the timeout. |
| `ssrManifestMaxAgeMs` | `number` | `30000` | Interval before the server re-checks a remote manifest for a new release. |
| `config` | `Partial<ModuleFederationOptions>` | See below | Options passed to `@module-federation/vite`. |
| `config` | `Partial<ModuleFederationOptions>` | See below | Options passed to the selected Vite or Rspack federation plugin. |

The MF Vite config defaults are:
The common federation config defaults are:

```ts
{
Expand Down
20 changes: 11 additions & 9 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
"name": "@module-federation/nuxt",
"version": "0.1.0",
"type": "module",
"description": "Nuxt module for Module Federation using @module-federation/vite",
"description": "Nuxt module for Module Federation using Vite or Rspack",
"license": "MIT",
"sideEffects": false,
"main": "./dist/federation.mjs",
"module": "./dist/federation.mjs",
"types": "./dist/federation.d.mts",
"types": "./dist/federation.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/module-federation/nuxt.git",
Expand All @@ -24,17 +24,17 @@
],
"exports": {
".": {
"types": "./dist/federation.d.mts",
"types": "./dist/federation.d.ts",
"import": "./dist/federation.mjs",
"default": "./dist/federation.mjs"
},
"./shared-strategy": {
"types": "./dist/shared-strategy.d.mts",
"types": "./dist/shared-strategy.d.ts",
"import": "./dist/shared-strategy.mjs",
"default": "./dist/shared-strategy.mjs"
},
"./ssr-entry-loader": {
"types": "./dist/ssr-entry-loader.d.mts",
"types": "./dist/ssr-entry-loader.d.ts",
"import": "./dist/ssr-entry-loader.mjs",
"default": "./dist/ssr-entry-loader.mjs"
},
Expand All @@ -50,8 +50,8 @@
"provenance": true
},
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch --no-clean",
"build": "rslib build",
"dev": "rslib build --watch --no-clean",
"pack:dry": "npm pack --dry-run",
"prepack": "pnpm run build",
"prepublishOnly": "pnpm run typecheck && pnpm run build",
Expand All @@ -61,18 +61,20 @@
"node": "^22.18.0 || ^24.11.0 || >=26.0.0"
},
"dependencies": {
"@module-federation/enhanced": "2.8.1",
"@module-federation/runtime": "2.8.1",
"@module-federation/vite": "1.20.0",
"@nuxt/kit": "^4.5.1",
"@nuxt/schema": "^4.5.1"
"@nuxt/schema": "^4.5.1",
"acorn": "8.15.0"
},
"peerDependencies": {
"nuxt": ">=4.5.1"
},
"devDependencies": {
"@rslib/core": "0.23.2",
"@types/node": "^26.1.2",
"nuxt": "4.5.1",
"tsdown": "^0.22.14",
"vue-tsc": "3.3.8"
}
}
Loading
Loading