vite-ssr-components
v0.6.1
Published
**_vite-ssr-components_** provides JSX components and Vite plugins for helping the development and deployment for SSR application with Vite.
Readme
vite-ssr-components
vite-ssr-components provides JSX components and Vite plugins for helping the development and deployment for SSR application with Vite.
Motivation
When using Cloudflare's Vite plugin for SSR applications, several challenges arise:
- Missing Vite client scripts: The Vite client script (
/@vite/client) needs to be manually embedded in server-side rendered HTML - No SSR hot reload: Server-side code changes don't trigger hot reloads during development
- Complex asset path resolution: Resolving script and asset paths after build requires manual
manifest.jsonhandling - Manual build configuration: Manually specifying entry files in
vite.config.tsfor each Script/Link component
This library solves these issues by providing:
- ViteClient component for development mode
- SSR hot reload plugin for seamless development experience
- Automatic asset path resolution using Vite's
manifest.json - Automatic build entry detection from Script/Link components with flexible component-attribute mapping
- Framework agnostic components for both hono/jsx and React
Install
npm i -D vite-ssr-componentsQuick Start
1. Add the SSR Plugin
// vite.config.ts
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import ssrPlugin from 'vite-ssr-components/plugin'
export default defineConfig({
plugins: [cloudflare(), ssrPlugin()],
})2. Use Components in Your SSR Code
import { Script, Link, ViteClient } from 'vite-ssr-components/hono'
// import { Script, Link, ViteClient } from 'vite-ssr-components/react'
function App() {
return (
<html>
<head>
<ViteClient />
<Script src='/src/client.tsx' />
<Link href='/src/style.css' rel='stylesheet' />
</head>
<body>
<div id='root' />
</body>
</html>
)
}That's it! The plugin automatically:
- Scans your source files for Script/Link components
- Detects the referenced files from component attributes
- Adds the detected files to Vite's build input
- Configures client build settings
- Enables SSR hot reload
Components
[!IMPORTANT] If you define custom components with the same names as the default
LinkandScriptcomponents, unexpected behavior may occur. In such cases, use different names for your custom components or specify custom component names in the plugin'scomponentsconfiguration.
ViteClient
Adds Vite client script for development mode.
import { ViteClient } from 'vite-ssr-components/hono'
// import { ViteClient } from 'vite-ssr-components/react'
function App() {
return (
<html>
<head>
<ViteClient />
</head>
</html>
)
}
// Renders: <script type="module" src="/@vite/client"></script>Script
Adds script tags with proper Vite handling. In production mode, automatically reads manifest.json to resolve the correct built file paths.
import { Script } from 'vite-ssr-components/hono'
// import { Script } from 'vite-ssr-components/react'
function App() {
return (
<html>
<head>
<Script src='/src/client.tsx' />
</head>
</html>
)
}
// Development: <script type="module" src="/src/client.tsx"></script>
// Production: <script type="module" src="/assets/client-abc123.js"></script>Options
src(required): Source path of the script filemanifest: Custom Vite manifest object (auto-loaded if not provided)prod: Force production mode (defaults toimport.meta.env.PROD)baseUrl: Base URL for assets (defaults to/)- All standard HTML script attributes are supported
<Script src='/src/client.tsx' baseUrl='/app/' defer onLoad={() => console.log('loaded')} />Link
Adds link tags for stylesheets and other resources. In production mode, automatically reads manifest.json to resolve the correct built file paths.
import { Link } from 'vite-ssr-components/hono'
// import { Link } from 'vite-ssr-components/react'
function App() {
return (
<html>
<head>
<Link href='/src/style.css' rel='stylesheet' />
</head>
</html>
)
}
// Development: <link href="/src/style.css" rel="stylesheet">
// Production: <link href="/assets/style-def456.css" rel="stylesheet">Options
href(required): Source path of the resourcemanifest: Custom Vite manifest object (auto-loaded if not provided)prod: Force production mode (defaults toimport.meta.env.PROD)baseUrl: Base URL for assets (defaults to/)- All standard HTML link attributes are supported
<Link href='/src/style.css' rel='stylesheet' baseUrl='/app/' media='screen' />ReactRefresh (React only)
Enables React Fast Refresh in development. Requires @vitejs/plugin-react to be installed and configured.
import { ReactRefresh } from 'vite-ssr-components/react'
function App() {
return (
<html>
<head>
<ReactRefresh />
</head>
</html>
)
}
// Adds React refresh runtime in development modePlugins
SSR Plugin
The main plugin that combines auto-entry detection and SSR hot reload functionality.
// vite.config.ts
import { defineConfig } from 'vite'
import ssrPlugin from 'vite-ssr-components/plugin'
export default defineConfig({
plugins: [ssrPlugin()],
})Options
interface Component {
name: string // Component name to detect
attribute: string // Attribute name to extract file path from
}
interface SSRPluginOptions {
entry?: {
target?: string | string[] // File patterns to scan (default: '**/*.{tsx,ts}')
components?: Component[] // Component configurations (default: [{ name: 'Script', attribute: 'src' }, { name: 'Link', attribute: 'href' }])
}
hotReload?:
| boolean
| {
target?: string | string[] // File patterns to watch (default: ['**/*.ts', '**/*.tsx'])
ignore?: string | string[] // File patterns to ignore
}
}When target / entry is omitted, both autoEntry and ssrHotReload scan
the whole project. The following directories are excluded automatically:
- Fixed:
node_modules,dist,build,out,coverage, plus any dotfile directories - Dynamic:
build.outDirand everyenvironments[*].build.outDirconfigured on the resolved Vite config
For autoEntry, files matching the pattern are also fast-skipped before
the AST parser runs when none of the configured component names appears as
a substring of the file — this keeps the wider default cheap on monorepos
and large codebases.
Examples
// Basic usage
export default defineConfig({
plugins: [ssrPlugin()],
})
// Custom file patterns and component configurations
export default defineConfig({
plugins: [
ssrPlugin({
entry: {
target: ['src/**/*.tsx', 'app/**/*.ts'], // Scan multiple patterns
components: [
{ name: 'Script', attribute: 'src' },
{ name: 'Link', attribute: 'href' },
{ name: 'CustomScript', attribute: 'source' },
{ name: 'CustomLink', attribute: 'url' },
{ name: 'Foo', attribute: 'bar' },
{ name: 'DataLoader', attribute: 'dataPath' },
{ name: 'AssetLoader', attribute: 'assetUrl' },
],
},
hotReload: {
target: ['src/**/*.ts', 'src/**/*.tsx'],
ignore: ['src/client/**/*'],
},
}),
],
})
// Scan only specific directories
export default defineConfig({
plugins: [
ssrPlugin({
entry: {
target: 'app/**/*.tsx', // Only scan app directory
},
}),
],
})
// Disable hot reload
export default defineConfig({
plugins: [
ssrPlugin({
hotReload: false,
}),
],
})Auto-Entry Detection
The plugin automatically scans your source files to detect Script/Link components and extracts file paths from their attributes. This eliminates the need to manually configure build entries.
How it works:
- File Scanning: The plugin scans files matching the specified patterns (default:
**/*.{tsx,ts}, excludingnode_modules,dist,build,out,coverage, dotfile dirs, and any configuredbuild.outDir) - Substring Prefilter: Files that don't even mention
vite-ssr-componentsare skipped before the AST parser runs - AST Analysis: Remaining files are parsed and analyzed to find JSX components
- Import-aware Detection: Only
<Script>/<Link>(or your custom components) imported fromvite-ssr-componentsare picked up. Same-named components from other packages (e.g.@inertiajs/react's<Link>,next/script) are ignored. - Attribute Extraction: File paths are extracted from the specified attributes
- Build Configuration: Detected files are automatically added to Vite's build input
Resolving the package
When vite-ssr-components is installed normally, the plugin also accepts the
following two import shapes as belonging to the package:
- Relative imports that resolve to a file inside the on-disk package directory
(useful in monorepos that reach into the package via
../../) - Bare specifiers under another name (e.g.
@my/ssr) whose resolvedpackage.jsondirectory matchesvite-ssr-components— covers workspace links / aliases that point at the same package on disk
Not supported
The following import shapes are not detected, because resolving them requires Vite's full resolver pipeline (which isn't available at the point where auto-entry runs):
tsconfig.jsonpathsaliases (e.g.@/components/Script)- Vite
resolve.aliasentries
If you rely on either, import the components via the canonical
vite-ssr-components/<entry> specifier in the files you want auto-entry to
scan.
Detected
src/hrefvalues are normalized to project-relative paths, so leading-slash forms like<Script src="/src/client.tsx" />are accepted by both rollup and Vite 8 / rolldown.
Example:
// src/pages/home.tsx
function HomePage() {
return (
<html>
<head>
<Script src='/src/client.tsx' />
<Link href='/src/style.css' rel='stylesheet' />
</head>
</html>
)
}
// src/pages/about.tsx
function AboutPage() {
return (
<html>
<head>
<Script src='/src/about-client.tsx' />
<Link href='/src/about.css' rel='stylesheet' />
</head>
</html>
)
}The plugin will automatically detect and add these files to the build:
/src/client.tsx/src/style.css/src/about-client.tsx/src/about.css
Custom Component Detection
The plugin can detect any custom components with any attribute names:
// Custom components in your SSR code
function App() {
return (
<html>
<head>
<CustomScript source='/src/app.js' />
<CustomLink url='/src/main.css' />
<Foo bar='/src/data.json' />
<DataLoader dataPath='/src/config.json' />
<AssetLoader assetUrl='/src/assets/image.png' />
</head>
</html>
)
}The plugin will automatically:
- Detect
CustomScriptcomponents and extract paths from thesourceattribute - Detect
CustomLinkcomponents and extract paths from theurlattribute - Detect
Foocomponents and extract paths from thebarattribute - And so on...
Each component type only looks for its specified attribute, ensuring accurate detection:
// Only the correct attributes are detected for each component
<Script href="/src/wrong.css" src="/src/client.tsx" /> // Only 'src' is detected
<Link src="/src/wrong.js" href="/src/style.css" /> // Only 'href' is detectedFile Pattern Matching
The plugin supports flexible file pattern matching using glob patterns:
// Single pattern
pattern: 'src/**/*.tsx'
// Multiple patterns
pattern: ['src/**/*.tsx', 'app/**/*.ts', 'components/**/*.jsx']
// Exclude specific directories
pattern: ['src/**/*.{tsx,ts}', '!src/test/**/*']Pattern Examples:
src/**/*.tsx- All .tsx files in src directory and subdirectoriesapp/**/*.{ts,tsx}- All .ts and .tsx files in app directory**/*.jsx- All .jsx files in the entire project!node_modules/**/*- Exclude node_modules directory
Examples
See the examples directory for complete working examples.
hono/jsx
Directory Structure
examples/hono/
├── src/
│ ├── index.tsx # Server entry point
│ ├── client.tsx # Client entry point
│ └── style.css # Stylesheet
├── vite.config.ts # Vite configuration
└── package.jsonvite.config.ts
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import ssrPlugin from 'vite-ssr-components/plugin'
export default defineConfig({
plugins: [cloudflare(), ssrPlugin()],
})Server Code
import { Hono } from 'hono'
import { Script, Link, ViteClient } from 'vite-ssr-components/hono'
const app = new Hono()
app.get('/', (c) => {
return c.html(
<html>
<head>
<ViteClient />
<Script src='/src/client.tsx' />
<Link href='/src/style.css' rel='stylesheet' />
</head>
<body>
<div id='root' />
</body>
</html>
)
})React
Directory Structure
examples/react/
├── src/
│ ├── index.tsx # Server entry point
│ ├── style.css # Stylesheet
│ └── client/
│ ├── index.tsx # Client entry point
│ └── app.tsx # React app component
├── vite.config.ts # Vite configuration
└── package.jsonvite.config.ts
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import react from '@vitejs/plugin-react'
import ssrPlugin from 'vite-ssr-components/plugin'
export default defineConfig({
plugins: [
cloudflare(),
ssrPlugin({
hotReload: {
ignore: ['./src/client/**/*.tsx'],
},
}),
react(),
],
})Server Code
import { Hono } from 'hono'
import { Script, Link, ViteClient, ReactRefresh } from 'vite-ssr-components/react'
import { renderToReadableStream } from 'react-dom/server'
const app = new Hono()
app.get('/', async (c) => {
c.header('Content-Type', 'text/html')
return c.body(
await renderToReadableStream(
<html>
<head>
<ViteClient />
<ReactRefresh />
<Script src='/src/client/index.tsx' />
<Link href='/src/style.css' rel='stylesheet' />
</head>
<body>
<div id='root' />
</body>
</html>
)
)
})Author
Yusuke Wada https://github.com/yusukebe
License
MIT
