renderPage()
The renderPage() function enables you to embed Vike into any server.
We generally recommend using
vike(app)instead ofrenderPage(), seerenderPage()vsvike(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.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, ...)
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 whilerenderPage()is lower-level
vike(app) | renderPage() | |
|---|---|---|
| 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).
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 Vite | Not processed by Vite |
| Server restart (2) | Fine-grained (only when needed) | 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. (Modern runtimes such as Bun and recent Node.js versions can run TypeScript, but they don't apply your Vite configuration.) $ vike devrestarts your server only when a file imported by+serverEntry.jschanges: modifying a page doesn't restart your server (the page is updated with HMR). A watcher such asnode --watchornodemondoesn't know about Vite's module graph.
See also: Integration >
+server> Extra control