@ssrx/vite v0.7.2
@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.jsonfile 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):
yarn add @ssrx/vite
yarn add -D vite@5This file should mount your application in the browser. For React it might look something like this:
// src/entry.client.tsx
import { hydrateRoot } from 'react-dom/client';
import { App } from '~/app.tsx';
hydrateRoot(document, <App />);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/viteis focused on supporting the WinterCG standard. Modern node frameworks such asHonoandh3, as well as alternative runtimes such asbun,deno,cloudflare, and more should all work well with this pattern.
For React, it might look something like this:
// src/server.ts
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',
},
});
},
};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 must adhere to the path-to-regex syntax
path?: string;
children?: Route[];
// If lazy or component.preload point to a dynamic import, that route will be code split
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.
Example:
import { ssrx } from '@ssrx/vite/plugin';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
// ... your other plugins
// The plugin, with all of it's defaults.
// You only need to set these options if they deviate from the defaults.
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.jsonand_headersdefaults.
Inspiration
Many thanks to these awesome libraries! Please check them out - they provided inspiration as I navigated my first Vite plugin.