Files
gt-agent-company/.agents/skills/vite/references/build-ssr.md
sexygoat 3c62cc1cd1 first
2026-03-31 18:41:52 +08:00

3.5 KiB

name, description
name description
build-ssr Server-side rendering setup and configuration with Vite

Server-Side Rendering (SSR)

Low-level API for framework authors. For applications, use higher-level tools from Awesome Vite SSR.

Project Structure

├── index.html
├── server.js              # Express/Node server
└── src/
    ├── main.js            # Universal app code
    ├── entry-client.js    # Mounts app to DOM
    └── entry-server.js    # Renders app with SSR API

index.html

<!DOCTYPE html>
<html>
  <body>
    <div id="app"><!--ssr-outlet--></div>
    <script type="module" src="/src/entry-client.js"></script>
  </body>
</html>

Development Server

// server.js
import express from 'express'
import { createServer as createViteServer } from 'vite'

async function createServer() {
  const app = express()

  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom'
  })

  app.use(vite.middlewares)

  app.use('*all', async (req, res, next) => {
    const url = req.originalUrl

    try {
      // 1. Read index.html
      let template = fs.readFileSync(
        path.resolve(__dirname, 'index.html'),
        'utf-8'
      )

      // 2. Apply Vite transforms
      template = await vite.transformIndexHtml(url, template)

      // 3. Load server entry
      const { render } = await vite.ssrLoadModule('/src/entry-server.js')

      // 4. Render app HTML
      const appHtml = await render(url)

      // 5. Inject into template
      const html = template.replace('<!--ssr-outlet-->', appHtml)

      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      vite.ssrFixStacktrace(e)
      next(e)
    }
  })

  app.listen(5173)
}

createServer()

Conditional Logic

if (import.meta.env.SSR) {
  // Server-only code (tree-shaken on client)
}

Production Build

{
  "scripts": {
    "build:client": "vite build --outDir dist/client",
    "build:server": "vite build --outDir dist/server --ssr src/entry-server.js"
  }
}

Production Server

// Differences from dev:
// 1. Use dist/client/index.html as template
// 2. Use import('./dist/server/entry-server.js') instead of ssrLoadModule
// 3. Serve static files from dist/client

SSR Manifest

For preload directives:

vite build --outDir dist/client --ssrManifest

Generates dist/client/.vite/ssr-manifest.json with module-to-chunk mappings.

SSR Externals

Dependencies are externalized by default. To transform with Vite:

export default defineConfig({
  ssr: {
    noExternal: ['package-that-needs-transform'],
    external: ['package-to-externalize']
  }
})

SSR-specific Plugin Logic

export function mySSRPlugin() {
  return {
    name: 'my-ssr',
    transform(code, id, options) {
      if (options?.ssr) {
        // SSR-specific transform
      }
    }
  }
}

SSR Target

export default defineConfig({
  ssr: {
    target: 'node',      // Default
    // target: 'webworker'  // For edge runtimes
  }
})

SSR Bundle

Bundle all dependencies (for workers):

export default defineConfig({
  ssr: {
    noExternal: true  // Bundle everything
  }
})

Resolve Conditions

export default defineConfig({
  ssr: {
    resolve: {
      conditions: ['node'],
      externalConditions: ['node']
    }
  }
})

Pre-Rendering / SSG

Pre-render routes with known data into static HTML at build time.