Vite and Server-Side Rendering: An Essential Guide

Vite and Server-Side Rendering: An Essential Guide

ยท

2 min read

๐Ÿš€ Vite + SSR (Server-Side Rendering) Custom Setup Guide

Vite supports SSR, but unlike Next.js, it requires a manual Express (or Fastify) server to handle rendering. Below is a custom SSR setup using Vite + React + Express.


๐Ÿ› ๏ธ Step 1: Create a Vite SSR Project

Run the following command to create a new Vite app:

npm create vite@latest my-ssr-app --template react

Then install dependencies:

cd my-ssr-app
npm install
npm install express @vitejs/plugin-react

๐Ÿ“ Step 2: Configure Vite for SSR

Modify vite.config.ts:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  ssr: {
    noExternal: ['react', 'react-dom'], // Ensure dependencies are bundled for SSR
  },
});

๐ŸŒŽ Step 3: Create an Express Server for SSR

Inside your project root, create a server folder. Inside it, create server.ts:

import express from 'express';
import { createServer as createViteServer } from 'vite';
import path from 'path';

async function startServer() {
  const app = express();

  const vite = await createViteServer({
    server: { middlewareMode: true },
  });

  app.use(vite.middlewares);

  app.use('*', async (req, res, next) => {
    try {
      const url = req.originalUrl;
      let template = await vite.transformIndexHtml(url, `
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Vite SSR</title>
        </head>
        <body>
          <div id="root"></div>
          <script type="module" src="/src/entry-client.tsx"></script>
        </body>
        </html>
      `);

      const { render } = await vite.ssrLoadModule('/src/entry-server.tsx');
      const appHtml = await render(url);

      res.status(200).set({ 'Content-Type': 'text/html' }).end(template.replace(`<div id="root"></div>`, `<div id="root">${appHtml}</div>`));
    } catch (error) {
      vite.ssrFixStacktrace(error);
      next(error);
    }
  });

  app.listen(3000, () => console.log('Server running at http://localhost:3000'));
}

startServer();

๐Ÿ“Œ Step 4: Create SSR Entry Files

Client Entry: src/entry-client.tsx

import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App';

hydrateRoot(document.getElementById('root')!, <App />);

Server Entry: src/entry-server.tsx

import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App';

export function render(url: string) {
  return renderToString(<App />);
}

๐Ÿ”„ Step 5: Update package.json Scripts

Modify package.json:

"scripts": {
  "dev": "node server/server.ts",
  "build": "vite build",
  "preview": "vite preview"
}

๐Ÿš€ Step 6: Run the SSR Server

npm run dev

Now visit localhost:3000 โ€“ Your React app will be rendered server-side first, then hydrated on the client!


๐Ÿ“Œ Extra Enhancements

โœ… Data Fetching with fetch() inside entry-server.tsx
โœ… Express API Routes for dynamic content
โœ… Fastify instead of Express for better performance

ย