Edit

renderPage()

Environment: server

The renderPage() function enables you to embed Vike into any server.

We generally recommend using vike(app) instead of renderPage(), see renderPage() vs vike(app).

You only need renderPage() if you use SSR, see Optional.

From the perspective of a server, Vike is just a middleware:

// +serverEntry.js
 
// renderPage() doesn't depend on Node.js and can be used within any JavaScript environment:
// Node.js, AWS, Cloudflare, Vercel, Deno, Bun, Lagon, ...
import { renderPage } from 'vike/server'
 
// Any server: Express.js, Cloudflare Worker, AWS Lambda Function, Fastify, Hono, Nitro, ...
server.addMiddleware({
  method: 'GET',
  route: '*', // catch-all
  async handler(request) {
    const pageContextInit = { urlOriginal: request.url }
    const pageContext = await renderPage(pageContextInit)
    // `body` is the HTML of the page with a route matching pageContextInit.urlOriginal
    const { body, statusCode, headers } = pageContext.httpResponse
    const response = { body, statusCode, headers }
    return response
  }
})
// +serverEntry.ts
 
// renderPage() doesn't depend on Node.js and can be used within any JavaScript environment:
// Node.js, AWS, Cloudflare, Vercel, Deno, Bun, Lagon, ...
import { renderPage } from 'vike/server'
 
// Any server: Express.js, Cloudflare Worker, AWS Lambda Function, Fastify, Hono, Nitro, ...
server.addMiddleware({
  method: 'GET',
  route: '*', // catch-all
  async handler(request: Request) {
    const pageContextInit = { urlOriginal: request.url }
    const pageContext = await renderPage(pageContextInit)
    // `body` is the HTML of the page with a route matching pageContextInit.urlOriginal
    const { body, statusCode, headers } = pageContext.httpResponse
    const response = { body, statusCode, headers }
    return response
  }
})

You can embed renderPage() into any server and any deployment environment.

You can also run your server yourself without +serverEntry.js (e.g. $ node server/index.js), see Without +serverEntry.js.

Alternatively, instead of using renderPage(), you can pre-render your pages and remove the need for a production server (and deploy to a static host instead).

Usage

Define your server at +serverEntry.js and use renderPage() as the last middleware.

Don't define +server.js, see API > +serverEntry > Development.

// +serverEntry.js
 
// In this example we use Express.js but we could use any other server framework
import express from 'express'
import { fileURLToPath } from 'node:url'
import { renderPage, createDevMiddleware } from 'vike/server'
 
// Create an Express.js server
const app = express()
 
// Dev/prod middleware
if (import.meta.env.DEV) {
  // Vite's development middleware (HMR, transpiling, static assets, ...)
  const { devMiddleware } = await createDevMiddleware()
  app.use(devMiddleware)
} else {
  // In production, we need to serve our static assets ourselves.
  // (In dev, Vite's middleware serves our static assets.)
  app.use(express.static(fileURLToPath(new URL('../client', import.meta.url))))
}
 
// ...
// Other middlewares (authentication, REST/GraphQL/RPC middleware, ...)
// ...
 
// SSR middleware.
// Note: it should always be the last middleware, because it's a catch-all
// middleware that supersedes any middleware placed after it.
app.get('/{*vikeCatchAll}', async (req, res) => {
  const pageContextInit = {
    // Required: the URL of the page
    urlOriginal: req.originalUrl,
 
    // Optional: the HTTP Headers
    headersOriginal: req.headers,
 
    // Optional: information about the logged-in user (when using an
    // Express.js authentication middleware that defines `req.user`).
    user: req.user
 
    // ... we can provide any additional information about the request here ...
  }
 
  const pageContext = await renderPage(pageContextInit)
 
  const { body, statusCode, headers } = pageContext.httpResponse
  headers.forEach(([name, value]) => {
    // There can be several Set-Cookie headers: res.setHeader() would only keep the last one
    if (name.toLowerCase() === 'set-cookie') res.appendHeader(name, value)
    else res.setHeader(name, value)
  })
  res.status(statusCode).send(body)
})
 
const port = 3000
app.listen(port)
console.log(`Server running at http://localhost:${port}`)
// +serverEntry.ts
 
// In this example we use Express.js but we could use any other server framework
import express from 'express'
import { fileURLToPath } from 'node:url'
import { renderPage, createDevMiddleware } from 'vike/server'
import type { Request, Response } from 'express'
 
// Create an Express.js server
const app = express()
 
// Dev/prod middleware
if (import.meta.env.DEV) {
  // Vite's development middleware (HMR, transpiling, static assets, ...)
  const { devMiddleware } = await createDevMiddleware()
  app.use(devMiddleware)
} else {
  // In production, we need to serve our static assets ourselves.
  // (In dev, Vite's middleware serves our static assets.)
  app.use(express.static(fileURLToPath(new URL('../client', import.meta.url))))
}
 
// ...
// Other middlewares (authentication, REST/GraphQL/RPC middleware, ...)
// ...
 
// SSR middleware.
// Note: it should always be the last middleware, because it's a catch-all
// middleware that supersedes any middleware placed after it.
app.get('/{*vikeCatchAll}', async (req: Request, res: Response) => {
  const pageContextInit = {
    // Required: the URL of the page
    urlOriginal: req.originalUrl,
 
    // Optional: the HTTP Headers
    headersOriginal: req.headers,
 
    // Optional: information about the logged-in user (when using an
    // Express.js authentication middleware that defines `req.user`).
    user: req.user
 
    // ... we can provide any additional information about the request here ...
  }
 
  const pageContext = await renderPage(pageContextInit)
 
  const { body, statusCode, headers } = pageContext.httpResponse
  headers.forEach(([name, value]) => {
    // There can be several Set-Cookie headers: res.setHeader() would only keep the last one
    if (name.toLowerCase() === 'set-cookie') res.appendHeader(name, value)
    else res.setHeader(name, value)
  })
  res.status(statusCode).send(body)
})
 
const port = 3000
app.listen(port)
console.log(`Server running at http://localhost:${port}`)
# Development: Vike runs your server with Vite and restarts it whenever you modify it
$ vike dev
# Build: +serverEntry.ts => dist/server/index.mjs
$ vike build
# Production
$ node dist/server/index.mjs

The pageContext.httpResponse.body value is the HTML string returned by the onRenderHtml() hook with additional <script> and <style> tags automatically injected by Vike.

You can control where and what Vike injects using API > injectFilter().

For HTML streams use httpResponse.pipe() instead of pageContext.httpResponse.body, see HTML Streaming. Same for binary and streamed non-HTML content.

Optionally, you can use pageContext.httpResponse.earlyHints for adding early hints (103 Early Hint), see Guides > Preloading > Early hints.

The renderPage() function doesn't depend on Node.js and you can use renderPage() (and therefore embed Vike) anywhere:

  • Any server environment (Express.js, Hattip, Deno, Fastify, Vite's development server, Node.js's HTTP server, ...)
  • Any deployment provider (AWS, Cloudflare Workers, Vercel, ...)

Optional

If you pre-render all your pages then renderPage() isn't needed, because:

  • Upon development ($ vike dev), Vike automatically embeds itself into Vite's development server.
  • Upon pre-rendering ($ vike build/$ vike prerender), Vike renders your pages without server.

But, if you use Server-Side Rendering (SSR), then you need a production server and renderPage() is required. See Guides > Pre-rendering (SSG).

renderPage() vs vike(app)

The fundamental difference is that:

  • vike(app) is more zero-config while renderPage() is lower-level
vike(app)renderPage()
ServersHono, Express, Fastify, H3, ElysiaAny
IntegrationOne lineYour own catch-all route
Custom pageContext propertiesWith +onCreatePageContextPassed to renderPage()
pageContext.req and pageContext.resSet for youPassed to renderPage() (if needed)
HTTP responseSent for youSent by you

vike(app) and renderPage() are interchangeable: you can use either one, regardless of whether you use +server.js, +serverEntry.js, or run your server directly (e.g. $ node server/index.js).

See also:

Without +serverEntry.js

You can also run your server yourself (e.g. $ node server/index.js) instead of +server.js and +serverEntry.js.

Unless you have a reason not to, we generally recommend using +server.js and/or +serverEntry.js as it provides a better development experience:

+server.js/+serverEntry.js$ node server/index.js
Server code (1)Processed by ViteNot processed by Vite
Server restart (2)Fine-grained (only when needed)Coarse (unnecessary reloads)
  1. Vite processes your server code: import.meta.env is available (e.g. import.meta.env.DEV and the variables of your .env files), and so are path aliases and Vite plugins. (Modern runtimes such as Bun and recent Node.js versions can run TypeScript, but they don't apply your Vite configuration.)
  2. $ vike dev restarts your server only when a file imported by +serverEntry.js changes: modifying a page doesn't restart your server (the page is updated with HMR). A watcher such as node --watch or nodemon doesn't know about Vite's module graph.

See also: Integration > +server > Extra control

See also