vite-plugin-remix3

🏎️ Vite plugin that unblocks Remix v3 apps to be developed, optimized and deployed everywhere with Nitro.

23
0
23
TypeScript
public

vite-plugin-remix3

Vite plugin that lets Remix v3 apps be developed, optimized, and deployed everywhere with Nitro.

Why?

Remix v3 is cool, but it’s runtime-only — every dependency ships to production unoptimized. Bundles balloon, cold starts drag, and cross-platform deployment becomes nearly impossible.

remix@3.0.0-beta.0 pulls in 112 dependencies from 63 maintainers (npm graph) and requires an on-demand build on server startup using tsx.

Pairing Remix with Vite and Nitro unlocks the build-time optimizations and deployment targets that were previously out of reach.

metric buildless (tsx) build (vite + nitro) smaller
ship size 163.33 MB 295.90 KB 565×
ship size (gzip) 58.29 MB 92.04 KB 649×
files 2,891 25 116×
build time 376 ms
startup time 389 ms 59 ms 6.6×
RSS at ready 155.01 MB 96.25 MB 1.6×

Benchmarks: source ran on Linux with Node.js v24.15.0 and are only an indicator.

How it works

Two plugins, one job each:

  • vite-plugin-remix3 tells Vite about your app’s two entry graphs — client (your app/entry.client.* files, bundled and hashed) and ssr (your server entry).
  • nitro/vite manages the server. In dev it serves requests through the SSR entry; in prod it produces a .output/ bundle that deploys to Node or anywhere else.

?assets=client imports are how SSR finds the right URLs for your client bundle. In components that render <script> / <link> tags:

import entryAssets from "../entry.client.ts?assets=client";
// entryAssets.entry → "/app/entry.client.ts" in dev
// entryAssets.entry → "/assets/app/entry.client-<hash>.js" in prod

In dev these resolve to source URLs that Vite transforms on the fly. In prod they resolve to hashed chunks served as static files from .output/public/. This replaces Remix’s runtime createAssetServer with build-time URLs. Thanks to vite-plugin-fullstack that is enabled out of the box in Nitro!

Getting started

Quickly start with the starter template:

npx -y giget gh:pi0/vite-plugin-remix3/starter remix3-app

Deployment

Nitro deployment is zero config and without any additional dependencies!

By default a portable Node.js server will be generated in .output/ dir and you can just run or deploy your app anywhere you want (read the docs)

Example deployments of starter template:

Migration

To migrate a stock Remix v3 starter onto Vite + Nitro:

1. Install and add vite.config.ts:

pnpm add -D vite nitro vite-plugin-remix3
// vite.config.ts
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
import { remix } from "vite-plugin-remix3";

export default defineConfig({ plugins: [nitro(), remix()] });

Update package.json scripts to vite dev / vite build / vite preview, and drop tsx.

2. Delete server.ts and app/assets.ts. Nitro provides the server; Vite replaces the runtime asset server. Also remove the routes.assets handler from app/router.ts (the route entry itself can stay).

3. Create app/entry.server.ts — the default ssrEntry. Nitro reads the SSR handler from its default export:

import { router } from "./router.ts";

export default { fetch: router.fetch };

4. Move app/assets/entry.tsapp/entry.client.ts and switch dynamic import() to import.meta.glob (Vite needs a static module map):

const modules = import.meta.glob([
  "/app/**/*.{ts,tsx,js,jsx}",
  "!/app/**/*.server.*",
  "!/app/**/*.d.ts",
]);

run({
  async loadModule(moduleUrl, exportName) {
    const key = moduleUrl.replace(/^\/assets/, "");
    const mod = await modules[key]();
    return mod[exportName];
  },
  // resolveFrame unchanged
});

5. Use the ?assets=client import in components that emit the entry <script> (e.g. document.tsx), so prod gets the hashed URL:

import entryAssets from "../entry.client.ts?assets=client";

{
  entryAssets.css.map((f) => <link rel="stylesheet" href={f.href} />);
}
<script type="module" src={entryAssets.entry} />;

6. Add .output to .gitignore.

[!IMPORTANT]
Don’t reintroduce a top-level server.ts importing app/router.ts — it pulls the rendering chain into Nitro’s build graph and the asset manifest comes out empty. See AGENTS.md.

License

Published under the MIT license 💛.

v0.3.3[beta]