npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@module-federation/tanstack

v0.1.1

Published

TanStack Start integration for Module Federation

Readme

@module-federation/tanstack

TanStack Start integration for Module Federation on Vite and Rsbuild. The adapter packages are optional peers, so importing one adapter does not resolve the other.

Support status

| Host | Remote | Browser federation | Federated SSR | | ------- | ------- | ------------------ | ---------------------------------- | | Vite | Vite | Supported | Supported | | Vite | Rsbuild | Supported | Supported (remote: server: true) | | Rsbuild | Vite | Supported | Supported (host: server: true) | | Rsbuild | Rsbuild | Supported | Supported (both: server: true) |

"Federated SSR" means the remote component is rendered into the host's initial HTML. Every row is covered by browser tests against both development servers and production builds: the remote hydrates, shares the host's React and context, responds to clicks, logs no errors, and the host falls back and recovers when the remote goes offline.

Vite

import { tanstackStartModuleFederation } from "@module-federation/tanstack/vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    tanstackStartModuleFederation({
      name: "host",
      remotes: {
        remote: {
          type: "module",
          name: "remote",
          entry: "https://example.test/remoteEntry.js",
        },
      },
    }),
    tanstackStart(),
    react(),
  ],
});

Place the federation wrapper before tanstackStart(), followed by the React plugin. The root import (@module-federation/tanstack) is the same Vite adapter.

Defaults:

  • filename: "remoteEntry.js", manifest: true, and hostInitInjectLocation: "entry".
  • react, react-dom, @tanstack/react-router, and @tanstack/router-core are shared singletons. When the config declares remotes, they are also eager, so the host's own React and Router runtimes stay in the share scope and a remote built by another bundler cannot replace them. Remote-only builds stay lazy.
  • Any entry you pass in shared replaces the default for that package.

A Vite host renders before lazy shared modules resolve, so mark any other shared package the host imports from its entry or root route as eager: true, as the wrapper does for React. Remotes can keep their shares lazy.

For a Vite host, configure SSR entry revalidation directly with the Vite option:

tanstackStartModuleFederation({
  name: "host",
  ssrEntryLoader: { maxAgeMs: 30_000 },
  // ...remotes and shared options
});

Vite's maxAgeMs revalidation is version-aware when the remote is resolved from an mf-manifest.json. Convention-resolved or direct server-entry URLs do not provide a remote version; use a manifest URL or call the Vite loader's explicit revalidate() when those URLs must be refreshed.

The wrapper omits the global Module Federation target option. TanStack Start's client and SSR environments need @module-federation/vite to select web and node independently.

The Vite adapter requires Vite 8, like TanStack Start's own examples and @vitejs/plugin-react 6. It stops with an error on older versions, where @module-federation/vite cannot load remotes on the server.

A Vite host renders Rsbuild remotes on the server when they are built with server: true (see below). Declare such a remote by its manifest, so the server can find the Node container:

remotes: {
  rsbuild_remote: {
    type: "global",
    name: "rsbuild_remote",
    entry: "https://example.test/mf-manifest.json",
  },
},

Rsbuild

import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";
import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild";
import { tanstackStartModuleFederation } from "@module-federation/tanstack/rsbuild";

export default defineConfig({
  plugins: [
    pluginReact(),
    tanstackStart(),
    ...tanstackStartModuleFederation({
      federation: {
        name: "remote",
        exposes: { "./StatusCard": "./src/StatusCard.tsx" },
      },
    }),
  ],
});

The adapter returns an array of Rsbuild plugins; spread it into plugins, after tanstackStart(). It emits script-compatible browser output, so the manifest can be consumed by Vite and Rsbuild hosts.

Defaults:

  • react, react-dom, @tanstack/react-router, and @tanstack/router-core are shared singletons, eager for hosts (configs with remotes) and lazy for remote-only builds.
  • experiments.asyncStartup: true, so entries wait for shared modules before running. Pass experiments: { asyncStartup: false } to opt out.
  • A remote's browser build uses publicPath: "auto", so hosts on other origins load its chunks from the remote. TanStack Start otherwise derives / from server.base. An absolute asset prefix, such as a CDN URL, is kept.

For hosts, set shareStrategy: "loaded-first". With the default version-first strategy, the host fetches every remote's manifest during startup, and a single offline remote fails the whole page.

SSR federation

Pass server: true (or an options object) to federate on the server:

  • Remotes (exposes) build a Node container, remoteEntry.ssr.cjs, next to remoteEntry.js in dist/client (its chunks go in dist/client/ssr/), and advertise it in mf-manifest.json as ssrRemoteEntry. Deploy dist/client as usual; the container holds only the exposed modules, never the app's server code. The remote's own server output is unchanged. Vite and Rsbuild hosts can both render it.
  • Hosts (remotes) load remotes on the server through the Module Federation Node runtime. TanStack Start's server output becomes async-node CommonJS (dist/server/index.cjs), with dist/server/index.js re-exporting it, so rsbuild preview and deployments that import index.js keep working. The dev server writes its server bundle to node_modules/.cache/tanstack-start-federation/ instead, so a production build in dist/server survives development sessions. Rsbuild remotes need server: true too. Vite remotes need nothing extra on their side, but the host must have @module-federation/vite (and its vite peer) installed: their server entries are ES modules, loaded by that package's SSR loader.

When a remote's server entry cannot load, the host's runtime plugin throws a RemoteEntryError whose phase is fetch, evaluate, or loader and whose cause is the original error.

Server entries are cached for the lifetime of the Node process by default. To pick up a rebuilt remote while keeping the process running, configure the revalidation window on an Rsbuild host:

tanstackStartModuleFederation({
  federation: {
    name: "host",
    remotes: {
      remote: "remote@https://example.test/mf-manifest.json",
    },
  },
  server: {
    ssrEntryLoader: {
      maxAgeMs: 30_000,
    },
  },
});

For an Rsbuild host, when maxAgeMs expires, the adapter invalidates the Module Federation remote container cache. Vite remotes also invalidate the Vite SSR entry-loader cache; the option is forwarded to that loader. strategy and fetchMaxBytes apply to Vite SSR entries, while fetchTimeoutMs applies to both Vite and CommonJS server-entry requests. Set either limit to 0 to disable it. Concurrent requests for an expired remote share one revalidation request on this adapter path.

Revalidation makes subsequent loads use the new remote entry and exposed modules, but does not safely replace already loaded process-level shared singletons such as React. Restart the host process after changing a shared singleton's version or implementation. For Rsbuild/CommonJS remotes, expiration also re-evaluates the downloaded server entry with a forced container re-registration. Module-scope side effects in a remote (for example timers or process listeners) are not automatically disposed, so use revalidation only for remotes whose evaluation is safe to repeat.

Rendering remotes on the server

Hosts on either bundler render remotes the same way: load the remote in the route loader, render it with lazyRemote inside a Suspense and error boundary, and link its stylesheets from the route's head:

import { getRemoteStylesheets, lazyRemote } from "@module-federation/tanstack/runtime";
import { createFileRoute } from "@tanstack/react-router";

const StatusCard = lazyRemote(() => import("remote/StatusCard"));

export const Route = createFileRoute("/")({
  // Loading the remote before rendering puts its markup in the server response.
  // Catch route-loader failures so an unavailable remote does not fail the route.
  loader: async () => {
    const [stylesheets] = await Promise.all([
      getRemoteStylesheets("https://remote.test/mf-manifest.json", "./StatusCard").catch(() => []),
      import("remote/StatusCard").catch(() => null),
    ]);
    return { stylesheets };
  },
  head: ({ loaderData }) => ({
    links: loaderData?.stylesheets.map((href) => ({ href, rel: "stylesheet" })),
  }),
  component: () => (
    <RemoteBoundary fallback="Loading…">
      <StatusCard />
    </RemoteBoundary>
  ),
});

RemoteBoundary stands for any error boundary around a Suspense; the examples in apps/ include one. The route loader must catch remote failures. During SSR, React does not guarantee that an error boundary switches to its fallback for a rejected lazy module; the fallback is handled during client rendering or hydration recovery instead.

  • lazyRemote(load, { retryAfterMs }) is React.lazy for remotes. A React.lazy component that fails once stays rejected until its lazy instance is recreated. lazyRemote recreates it on the first render after retryAfterMs (5 seconds by default), so a later request can retry a remote that recovered. This retry policy does not make an SSR error boundary render its fallback. The import itself loads again after a failure too: @module-federation/vite 1.23.3+ retries failed server loads, and the Rsbuild adapter removes a failed remote module from Rspack's module cache.
  • getRemoteStylesheets(manifestUrl, expose, { maxAgeMs }) lists the stylesheets the remote's manifest declares for an expose, as absolute URLs. A remote's CSS otherwise loads with its JavaScript, after the HTML, so the server-rendered markup would be unstyled until then. It works in the browser and on the server, reuses a fetched manifest for 30 seconds (maxAgeMs), and rejects when the manifest is unreachable or does not list the expose.

Known limitations

  • In development, a Vite remote's server modules import React from the files its dev server resolves, so host and remote must resolve React to the same files, as they do in a monorepo. Production builds take React from the host's share scope.
  • A Vite dev server injects CSS from JavaScript, so its manifest lists no stylesheets and getRemoteStylesheets returns none for it in development.

Requirements

  • Node 22.18+, 24.11+, or 26+.
  • TanStack Start 1.167.43+ (the first release with the Rsbuild integration).
  • React 19, which the tests use. TanStack Start also accepts React 18, but as of 1.168 its Rsbuild server build fails with it: TanStack Router imports React's use, which React 18 lacks. React 18 with Vite builds but is untested.
  • Vite: @module-federation/vite 1.23.3+ and vite 8.
  • Rsbuild: @module-federation/rsbuild-plugin and @rsbuild/core. An Rsbuild host that renders Vite remotes on the server also needs @module-federation/vite 1.23.3+ and vite.