Environment: server.

The renderPage() function enables you to embed Vike into your server. It's optional and only needed if you use SSR.

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

// 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, Fastify, Hono, Cloudflare Worker, AWS Lambda Function, ...
  method: 'GET',
  route: '*', // catch-all
  async handler(request) {
    const pageContextInit = { urlOriginal: request.url }
    const pageContext = await renderPage(pageContextInit)
    const { body, statusCode, headers } = pageContext.httpResponse
    const response = { body, statusCode, headers }
    return response

It's versatile and can be embedded into any server.

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



// server/index.js

// In this example we use Express.js but we could use any other server framework
import express from 'express'
import { renderPage } from 'vike/server'

const isProduction = process.env.NODE_ENV === 'production'
const root = `${__dirname}/..`


async function startServer() {
  // Create an Express.js server
  const app = express()

  // Vite integration
  if (!isProduction) {
    // We instantiate Vite's development server and integrate its middleware to our server.
    // ⚠️ We instantiate it only in development. (It isn't needed in production and it
    // would unnecessarily bloat our production server.)
    const vite = await import('vite')
    const viteDevMiddleware = (await vite.createServer({
      server: { middlewareMode: true }
  } else {
    // In production, we need to serve our static assets ourselves.
    // (In dev, Vite's middleware serves our static assets.)

  // ...
  // Other middlewares (e.g. some RPC middleware such as Telefunc)
  // ...

  // Vike middleware. It should always be our last middleware (because it's a
  // catch-all middleware superseding any middleware placed after it).
  app.get('*', async (req, res, next) => {
    const pageContextInit = { urlOriginal: req.originalUrl }
    const pageContext = await renderPage(pageContextInit)
    if (pageContext.httpResponse === null) return next()
    const { body, statusCode, headers } = pageContext.httpResponse
    headers.forEach(([name, value]) => res.setHeader(name, value))

  const port = 3000
  console.log(`Server running at http://localhost:${port}`)

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

For HTML streams use httpResponse.pipe() instead of pageContext.httpResponse.body, see Guides > HTML Streaming.

The pageContext.httpResponse value is null if:

  • An error occurred while rendering the page and no error page is defined.
  • An error occurred while rendering the error page.
  • Vike skips certain URLs, such as /favicon.ico (because browsers automatically make favicon requests).

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, ...)

When modifying your server, you may need to manually restart your server for your changes to take effect. See #562.

See also:


If you pre-render all your pages then you don't need to use renderPage(), because:

  • Vike automatically embeds itself into Vite's development server ($ vite/$ vite dev) and Vite's preview server ($ vite preview).
  • During pre-rendering ($ vite build/$ vike prerender) Vike automatically renders your pages.

If you use server-side rendering (SSR) and you don't pre-render all your pages, then you need a production server and you need to use renderPage() to embed Vike into your server.