Programming, error messages and sample code

How to publish a ReactJS Vite SSR to your hosting account

Programming, error messages and sample code > Node.js

1. Create a React project using Vite

 
npm create vite@latest
Enter your project name (this sample uses vite-project) and press Enter.   Use the arrow keys (↑ or ↓) to choose the React framework and press Enter.   Select the TypeScript variant and press Enter.   Navigate to the project folder, install the dependencies, and run the development server:  
cd vite-project
npm install
npm run dev
The project will run at http://localhost:5173/ by default. Update your pages, apply changes, and verify that everything works.   Press Ctrl+C to stop the server.  

2. Add Server-Side Rendering (SSR) support to your React project

  Install the Express, compression and sirv packages:  
cd vite-project
npm install express compression sirv
Open index.html in the project root. Add <!--app-head--> immediately after the <title> tag, and replace the contents of the <body> tag with the following:  
<div id="app"><!--app-html--></div>
<script type="module" src="/src/entry-client.tsx"></script>
index.html
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
    <!--app-head-->
</head>

<body>
    <div id="app"><!--app-html--></div>
    <script type="module" src="/src/entry-client.tsx"></script>
</body>

</html>
Create a server.js file in the project root and add the following code:
import fs from 'node:fs/promises'
import express from 'express'

// Constants
const isProduction = process.env.NODE_ENV === 'production'
const port = process.env.PORT || 5173
const base = process.env.BASE || '/'

// Cached production assets
const templateHtml = isProduction
    ? await fs.readFile('./dist/client/index.html', 'utf-8')
    : ''

// Create http server
const app = express()

// Add Vite or respective production middlewares
/** @type {import('vite').ViteDevServer | undefined} */
let vite
if (!isProduction) {
    const { createServer } = await import('vite')
    vite = await createServer({
        server: { middlewareMode: true },
        appType: 'custom',
        base,
    })
    app.use(vite.middlewares)
} else {
    const compression = (await import('compression')).default
    const sirv = (await import('sirv')).default
    app.use(compression())
    app.use(base, sirv('./dist/client', { extensions: [] }))
}

// Serve HTML
app.use('*all', async (req, res) => {
    try {
        const url = req.originalUrl.replace(base, '')

        /** @type {string} */
        let template
        /** @type {import('./src/entry-server.js').render} */
        let render
        if (!isProduction) {
            // Always read fresh template in development
            template = await fs.readFile('./index.html', 'utf-8')
            template = await vite.transformIndexHtml(url, template)
            render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render
        } else {
            template = templateHtml
            render = (await import('./dist/server/entry-server.js')).render
        }

        const rendered = await render(url)

        const html = template
            .replace(`<!--app-head-->`, rendered.head ?? '')
            .replace(`<!--app-html-->`, rendered.html ?? '')

        res.status(200).set({ 'Content-Type': 'text/html' }).send(html)
    } catch (e) {
        vite?.ssrFixStacktrace(e)
        console.log(e.stack)
        res.status(500).end(e.stack)
    }
})

// Start http server
app.listen(port, () => {
    console.log(`Server started at http://localhost:${port}`)
})
In the src folder, create entry-client.tsx and entry-server.tsx.   entry-client.tsx
import './index.css'
import { StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import App from './App'

hydrateRoot(
    document.getElementById('app') as HTMLElement,
    <StrictMode>
        <App />
    </StrictMode>,
)
entry-server.tsx
import { StrictMode } from 'react'
import { renderToString } from 'react-dom/server'
import App from './App'

/**
 * @param {string} _url
 */
export function render(_url) {
    const html = renderToString(
        <StrictMode>
            <App />
        </StrictMode>,
    )
    return { html }
}
Update the scripts section in your package.json file as follows:
"scripts": {
    "dev": "node server",
    "build:client": "vite build --outDir dist/client",
    "build:server": "vite build --outDir dist/server --ssr src/entry-server.tsx",
    "build": "npm run build:client && npm run build:server",
    "start": "node server.js",
},
Build the project:  
npm run build
Run the following command to start the site locally and confirm it works:  
npm run start
Visit http://localhost:5173/ to verify your web pages.  

3. Deploy the project

  Compress the entire vite-project folder into a .zip file.   Upload the .zip file via FTP.   Extract the .zip file using your hosting control panel.   Enable Node.js for your site within the hosting control panel.   Visit your website to verify the deployment.  

Reference:

  The final folder structure:  
site-root-folder/
  |-- dist/
    |-- client/
      |-- index.html
    |-- server/
      |-- entry-server.js
  |-- node_modules/
  |-- public/
  |-- src/
    |-- assets/
    |-- App.css
    |-- App.tsx
    |-- entry-client.tsx
    |-- entry-server.tsx
    |-- index.css
  |-- index.html
  |-- package.json
  |-- server.js