@ssrx/vite
A Vite plugin that improves the DX of developing SSR apps.
It is:
- ✅ Framework agnostic on the client (use react, solid, etc)
- ✅ Framework agnostic on the server (use node 18+, hono, h3, cloudflare, bun, deno, etc)
- ✅ Simple "native" Vite - continue using
vite dev
, vite build
, etc
It enables:
- Route based code-spliting with asset pre-loading
- Typescript + HMR support on the client AND server
- Elimates FOUC css issues during development
- Generates a
ssr-manifest.json
file during build that maps client route urls -> assets - Provides a
assetsForRequest(url: string)
function on the server that returns a list of assets critical to the given
request (along with preload links, etc). You can use this to inject the appropriate asset tags.
❗ A small disclaimer... SSRx intentionally does not try to do everything and is intended for a specific audience. If
you're looking for a full-fledged framework, SSRx might not be for you. If you are looking to build a modern SSR app
with your choice of 3rd party libraries for routing, head management, etc, then SSRx might be right for you.
❗ Remix is transitioning to Vite, so for Vite + React Router projects I now recommend Remix as the best-in-class
option.
Examples
The SSRx Vite plugin is barebones and (mostly) unopinionated by design, and can be used standalone. See the
bun-react-router
, react-router-simple
,
tanstack-router-simple
, and solid-router-simple
examples.
Usage
@ssrx/vite
is mostly unopinionated, but does require 3 things (the file locations are configurable, defaults below):
Install deps via yarn, npm, etc
yarn add @ssrx/vite
yarn add -D vite@5
Requirement #1 - a client entry file, `src/entry.client.tsx`
This file should mount your application in the browser. For React it might look something like this:
import { hydrateRoot } from 'react-dom/client';
import { App } from '~/app.tsx';
hydrateRoot(document, <App />);
Requirement #2 - a server file, `src/server.ts`
A server entry who's default export includes a fetch
function that accepts a
Request and returns a
Response object with your rendered or streamed app.
@ssrx/vite
is focused on supporting the WinterCG standard. Modern node frameworks such as Hono
and h3
, as well
as alternative runtimes such as bun
, deno
, cloudflare
, and more should all work well with this pattern.
For React, it might look something like this:
import { renderToString } from 'react-dom/server';
import { App } from '~/app.tsx';
export default {
fetch(req: Request) {
const html = renderToString(<App />);
return new Response(html, {
headers: {
'Content-Type': 'text/html',
},
});
},
};
Requirement #3 - a routes file, `src/routes.tsx`
Your routes file should export a routes
object. By default @ssrx/vite
expects the routes
object to conform to the
following shape:
type Route = {
path?: string;
children?: Route[];
lazy?: () => Promise<any>;
component?: {
preload?: () => Promise<any>;
};
};
react-router
and solid-router
both conform to this shape out of the box. You can provide your own routerAdapter
if
your routes config does not - see plugin-tanstack-router for an example.
Finally, update your vite.config.js
Example:
import { ssrx } from '@ssrx/vite/plugin';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
ssrx({
routesFile: 'src/routes.tsx',
clientEntry: 'src/entry.client.tsx',
serverFile: 'src/server.ts',
clientOutDir: 'dist/public',
serverOutDir: 'dist',
runtime: 'node',
routerAdapter: defaultRouterAdapter,
}),
],
});
Runtimes
The ssrx
vite plugin accepts a runtime
option. The available values are:
node
(default)edge
: adjusts Vite to bundle the server output into a single file, and sets resolve conditions that are more
appropriate for ssr / server rendering in popular edge environments.cf-pages
: adjust the output to be suitable for deployment to Cloudflare Pages, including generating sane
_routes.json
and _headers
defaults.
Inspiration
Many thanks to these awesome libraries! Please check them out - they provided inspiration as I navigated my first Vite
plugin.