renderPage()
The renderPage() function renders your pages (e.g. +Page) on the server-side.
⚠️We recommend using
vike(app)instead ofrenderPage(). BecauserenderPage()doesn't support middlewares added by Vike extensions (+middleware). See also:renderPage()vsvike(app).
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 also run your server yourself (e.g.
$ node server/index.js) instead of using+serverEntry.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> With(out)+server.js.
// +serverEntry.js
// In this example we use Express.js but we could use any other server framework
import express from 'express'
import assert from 'node:assert'
import { fileURLToPath } from 'node:url'
import { getGlobalContext } from 'vike'
import { renderPage } 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 getGlobalContext()
assert(devMiddleware)
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 assert from 'node:assert'
import { fileURLToPath } from 'node:url'
import { getGlobalContext } from 'vike'
import { renderPage } 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 getGlobalContext()
assert(devMiddleware)
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.mjsThe 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, ...)
renderPage() vs vike(app)
There are two fundamental differences:
vike(app)supports and automatically installs+middleware, whilerenderPage()doesn't support+middleware.This means that, if you use a Vike extension that uses
+middleware, then usevike(app)instead ofrenderPage().In principle, it's possible to use
renderPage()together with+middleware, but we don't recommend it.vike(app)is zero-config whilerenderPage()is lower-level.
vike(app) | renderPage() | |
|---|---|---|
+middleware | ✅ | ❌ |
| Servers | Hono, Express, Fastify, H3, Elysia | Any |
| Integration | One line | Your own catch-all route |
Custom pageContext properties | With +onCreatePageContext | Passed to renderPage() |
pageContext.req and pageContext.res | Set for you | Passed to renderPage() (if needed) |
| HTTP response | Sent for you | Sent by you |
vike(app)andrenderPage()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).That said, if you use a Vike extension that uses
+middleware, you cannot (easily) userenderPage()— you must usevike(app)(or useglobalContext.middlewaresif your server doesn't have avike(app)adapter).
See also:
Without +serverEntry.js
You can also run your server yourself (e.g. $ node server/index.js) instead of defining +server.js/+serverEntry.js.
Unless you have a reason, we generally recommend using +server.js/+serverEntry.js as it provides a better development experience:
+server.js/+serverEntry.js | $ node server/index.js | |
|---|---|---|
| Server code (1) | Processed by Vite | Not processed by Vite |
| Server restart (2) | Fine-grained | Coarse (unnecessary reloads) |
- Vite processes your server code:
import.meta.envis available (e.g.import.meta.env.DEVand the variables of your.envfiles), and so are path aliases and Vite plugins.While modern runtimes such as Bun and recent Node.js versions can run TypeScript, they don't apply your Vite configuration.
- Vike restarts your server process only when a file imported by
+serverEntry.jschanges: modifying a page doesn't restart your server (the page is updated with HMR).If you run the server yourself, you'll need a watcher (e.g.
node --watchornodemon) which doesn't know about Vite's module graph.
// server/index.js
import express from 'express'
import { fileURLToPath } from 'node:url'
import { createDevMiddleware } from 'vike/server'
const app = express()
if (process.env.NODE_ENV === 'production') {
app.use(express.static(fileURLToPath(new URL('../dist/client', import.meta.url))))
} else {
// Vite's development middleware (HMR, transpiling, static assets, ...)
const root = fileURLToPath(new URL('..', import.meta.url))
const { devMiddleware } = await createDevMiddleware({ root })
app.use(devMiddleware)
}
// ...
// Other middlewares and the SSR middleware, as in the example above
// ...
app.listen(3000)See also: