The vike-photon extension integrates Vike with any JavaScript server (Express.js, Hono, Fastify, ...) and deployment provider (Cloudflare, Vercel, self-hosting, ...) in a seamless fashion. It's powered by Photon.
By default, Photon uses a built-in server that supports basic features like SSR. If you need additional server functionalities (e.g. file uploads or API routes), then create your own server.
Server
By default, Photon uses a built-in server that supports basic features like SSR. If you need additional server functionalities (e.g. file uploads or API routes), then create your own server:
The serve() function enables you to define a single server entry while being able to target multiple environments (e.g. Node.js and Cloudflare have different server attachment styles).
serve(app, { // Server port, defaults to 3000. It's ignored in Cloudflare and Vercel Edge (there // isn't any server in serverless deployment). port: 3000, hostname: 'localhost', // default value // Called once the server is accepting connections onReady() { console.log('Server is ready.') }, // Called once the server is created onCreate(server) { // `server` type depends on your runtime: // Node.js: Server ('node:http') by default. It's an HTTPS or HTTP2 server // if the `createServer` option was provided (see below). // Deno: return of Deno.Serve (experimental support) // Bun: return of Bun.Serve (experimental support) // `server` is `undefined` in Cloudflare and Vercel Edge (there // isn't any server in serverless deployment) }, // ⚠️ The following two options are available only when running on Node.js // [Node.js] Can be one of: // import { createServer } from 'node:http' // import { createServer } from 'node:https' // import { createSecureServer as createServer } from 'node:http2' createServer, // [Node.js] Options forwarded to `createServer()` serverOptions: { // For example SSL/TLS key and certificate for HTTPS key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem') // ... other createServer() options ... }, // 👉 Other options are passed down as-is to the target environment // For example, you can define all @hono/node-serve options here, such as: fetch, overrideGlobalObjects: false // ... any options of your target environment ...})
serve(app, { // Server port, defaults to 3000. It's ignored in Cloudflare and Vercel Edge (there // isn't any server in serverless deployment). port: 3000, hostname: 'localhost', // default value // Called once the server is accepting connections onReady() { console.log('Server is ready.') }, // Called once the server is created onCreate(server) { // `server` type depends on your runtime: // Node.js: Server ('node:http') by default. It's an HTTPS or HTTP2 server // if the `createServer` option was provided (see below). // Deno: return of Deno.Serve (experimental support) // Bun: return of Bun.Serve (experimental support) // `server` is `undefined` in Cloudflare and Vercel Edge (there // isn't any server in serverless deployment) }, // ⚠️ The following two options are available only when running on Node.js // [Node.js] Can be one of: // import { createServer } from 'node:http' // import { createServer } from 'node:https' // import { createSecureServer as createServer } from 'node:http2' createServer, // [Node.js] Options forwarded to `createServer()` serverOptions: { // For example SSL/TLS key and certificate for HTTPS key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem'), // ... other createServer() options ... }, // 👉 Other options are passed down as-is to the target environment // For example, you can define all @hono/node-serve options here, such as: fetch, overrideGlobalObjects: false, // ... any options of your target environment ...})
pageContext.runtime
You can use pageContext.runtime to access the HTTP request object, for example:
// pages/product/@id/+data.jsexport async function data(pageContext) { // Using pageContext.runtime.req to access the logged-in user const { user } = pageContext.runtime.req // ...}
// pages/product/@id/+data.tsimport type { PageContextServer } from 'vike/types'export type Data = Awaited<ReturnType<typeof data>>export async function data(pageContext: PageContextServer) { // Using pageContext.runtime.req to access the logged-in user const { user } = pageContext.runtime.req // ...}declare global { namespace Vike { interface Photon { // 👉 Pick your server (for correct pageContext.runtime type) server: 'hono' // | 'express' | 'fastify' | 'hattip' | 'srvx' | 'elysia' | 'h3' } }}
With standalone: true, the build output directory (dist/) contains everything needed for deployment. This means that, in production, only the dist/ directory is required (you can remove node_modules/ and skip $ npm install).
Skipping node_modules/ massively reduces disk usage in production, which can be important, for example to reduce the size of Docker images.
export const config = { photon: { standalone: { // Default values bundle: false, // true => bundle output (usually a single file) minify: false, // true => minify output trace: true, // false => disable dependency tracing external: [] // List of packages to exclude from bundling } }}
import type { Config } from 'vike/types'export const config = { photon: { standalone: { // Default values bundle: false, // true => bundle output (usually a single file) minify: false, // true => minify output trace: true, // false => disable dependency tracing external: [] // List of packages to exclude from bundling } }} satisfies Config
With bundle: true and minify: true you can significantly reduce size, but we recommend against this because it makes debugging more difficult (stack traces are harder to read). Adding source maps doesn't solve the issue, because dist/ will now include source map files and may even end up larger.
If a package throws ENOENT: no such file or directory then either add it to external or ask the package maintainers to fix the issue.
The issue is that some packages (only few in our experience) reference files in a dynamic fashion that isn't statically analyzable and, consequently, cannot be included in the standalone bundle.
If you change a server file, the server code is automatically updated: the next HTTP response will be generated by the latest code. No full server reload is required.
This is experimental and doesn't always work: vike-photon sometimes still triggers a full server reload.
If HMR isn't what you want (for example if you modify the database connection) you can manually trigger a full server reload by pressing r + enter.
Compression
In production, Photon compresses all Vike responses.
You can disable it, or enable it only for static files:
If you want to use HTTPS in development, pass createServer and the HTTPS certificates to serve():
import { createServer } from 'node:https'/* Or with HTTP2:import { createSecureServer as createServer } from 'node:http2'*/serve(app, { createServer, // [Node.js] Options forwarded to `createServer()` serverOptions: { // For example SSL/TLS key and certificate for HTTPS key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem') // ... other createServer() options ... }})
import { createServer } from 'node:https'/* Or with HTTP2:import { createSecureServer as createServer } from 'node:http2'*/serve(app, { createServer, // [Node.js] Options forwarded to `createServer()` serverOptions: { // For example SSL/TLS key and certificate for HTTPS key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem') // ... other createServer() options ... },})