https://github.com/yusukebe/vite-ssr-components
JSX Components and Vite plugins for SSR apps
https://github.com/yusukebe/vite-ssr-components
Last synced: about 2 months ago
JSON representation
JSX Components and Vite plugins for SSR apps
- Host: GitHub
- URL: https://github.com/yusukebe/vite-ssr-components
- Owner: yusukebe
- Created: 2025-05-21T06:50:15.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2026-04-28T01:39:29.000Z (about 2 months ago)
- Last Synced: 2026-04-28T03:12:43.695Z (about 2 months ago)
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/vite-ssr-components
- Size: 149 KB
- Stars: 63
- Watchers: 2
- Forks: 2
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
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.json` handling
- **Manual build configuration**: Manually specifying entry files in `vite.config.ts` for 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
```bash
npm i -D vite-ssr-components
```
## Quick Start
### 1. Add the SSR Plugin
```ts
// 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
```tsx
import { Script, Link, ViteClient } from 'vite-ssr-components/hono'
// import { Script, Link, ViteClient } from 'vite-ssr-components/react'
function App() {
return (
)
}
```
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 `Link` and `Script` components, unexpected behavior may occur. In such cases, use different names for your custom components or specify custom component names in the plugin's `components` configuration.
### ViteClient
Adds Vite client script for development mode.
```tsx
import { ViteClient } from 'vite-ssr-components/hono'
// import { ViteClient } from 'vite-ssr-components/react'
function App() {
return (
)
}
// Renders:
```
### Script
Adds script tags with proper Vite handling. In production mode, automatically reads `manifest.json` to resolve the correct built file paths.
```tsx
import { Script } from 'vite-ssr-components/hono'
// import { Script } from 'vite-ssr-components/react'
function App() {
return (
)
}
// Development:
// Production:
```
#### Options
- `src` (required): Source path of the script file
- `manifest`: Custom Vite manifest object (auto-loaded if not provided)
- `prod`: Force production mode (defaults to `import.meta.env.PROD`)
- `baseUrl`: Base URL for assets (defaults to `/`)
- All standard HTML script attributes are supported
```tsx
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.
```tsx
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 resource
- `manifest`: Custom Vite manifest object (auto-loaded if not provided)
- `prod`: Force production mode (defaults to `import.meta.env.PROD`)
- `baseUrl`: Base URL for assets (defaults to `/`)
- All standard HTML link attributes are supported
```tsx
<Link href='/src/style.css' rel='stylesheet' baseUrl='/app/' media='screen' />
```
### ReactRefresh (React only)
Enables React Fast Refresh in development. Requires [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react) to be installed and configured.
```tsx
import { ReactRefresh } from 'vite-ssr-components/react'
function App() {
return (
<html>
<head>
<ReactRefresh />
</head>
</html>
)
}
// Adds React refresh runtime in development mode
```
## Plugins
### SSR Plugin
The main plugin that combines auto-entry detection and SSR hot reload functionality.
```ts
// vite.config.ts
import { defineConfig } from 'vite'
import ssrPlugin from 'vite-ssr-components/plugin'
export default defineConfig({
plugins: [ssrPlugin()],
})
```
#### Options
```ts
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.outDir` and every `environments[*].build.outDir`
configured 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
```ts
// 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:**
1. **File Scanning**: The plugin scans files matching the specified patterns (default: `**/*.{tsx,ts}`, excluding `node_modules`, `dist`, `build`, `out`, `coverage`, dotfile dirs, and any configured `build.outDir`)
2. **Substring Prefilter**: Files that don't even mention `vite-ssr-components` are skipped before the AST parser runs
3. **AST Analysis**: Remaining files are parsed and analyzed to find JSX components
4. **Import-aware Detection**: Only `<Script>` / `<Link>` (or your custom components) imported from `vite-ssr-components` are picked up. Same-named components from other packages (e.g. `@inertiajs/react`'s `<Link>`, `next/script`) are ignored.
5. **Attribute Extraction**: File paths are extracted from the specified attributes
6. **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 resolved
`package.json` directory matches `vite-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.json` `paths` aliases (e.g. `@/components/Script`)
- Vite `resolve.alias` entries
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` / `href` values 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:**
```tsx
// 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:
```tsx
// 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 `CustomScript` components and extract paths from the `source` attribute
- Detect `CustomLink` components and extract paths from the `url` attribute
- Detect `Foo` components and extract paths from the `bar` attribute
- And so on...
Each component type only looks for its specified attribute, ensuring accurate detection:
```tsx
// 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 detected
```
#### File Pattern Matching
The plugin supports flexible file pattern matching using glob patterns:
```ts
// 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 subdirectories
- `app/**/*.{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](./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.json
```
#### vite.config.ts
```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
```tsx
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.json
```
#### vite.config.ts
```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
```tsx
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