vike-photon
Version history: CHANGELOG.md
Examples: examples/
Source code: GitHub > vikejs/vike-photon
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.
The entire documentation for using Photon is contained on this page, Deploy > Cloudflare and Deploy > Vercel.
Photon is currently in beta — you can use it in production, but expect breaking changes more frequently than usual.
Alternatively, instead of using a server, you can pre-render your pages and deploy to a static host.
If you want more control over server integration, see Integration > Server integration > Manual integration.
Get started
New app
Use vike.dev/new to scaffold a new Vike app that uses Photon.
Add to existing app
If you're using
vike-serverthen migrate tovike-photon.
This guide is for self-hosted servers. For deploying to Cloudflare or Vercel, see:
1. Install
- npm
- pnpm
- yarn
npm i vike-photon// pages/+config.js
import vikePhoton from 'vike-photon/config'
export const config = {
extends: [vikePhoton]
}// pages/+config.ts
import type { Config } from 'vike/types'
import vikePhoton from 'vike-photon/config'
export const config = {
extends: [vikePhoton]
} satisfies Config2. Scripts
Update your production script in package.json:
// package.json
"scripts": {
"dev": "vike dev",
"build": "vike build",
"prod": "vike build && node ./dist/server/index.mjs"
}3. Server (optional)
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:
// pages/+config.js
import vikePhoton from 'vike-photon/config'
export default {
extends: [vikePhoton],
photon: {
server: 'server/index.js'
}
}// pages/+config.ts
import type { Config } from 'vike/types'
import vikePhoton from 'vike-photon/config'
export default {
extends: [vikePhoton],
photon: {
server: 'server/index.ts'
}
} satisfies Config- Hono
- Express.js
- Fastify
- H3
- Elysia
- Hattip
- npm
- pnpm
- yarn
npm i hono @photonjs/hono// server/index.js
import { Hono } from 'hono'
import { apply, serve } from 'vike-photon/hono'
function startServer() {
const app = new Hono()
apply(app)
return serve(app)
}
export default startServer()// server/index.ts
import { Hono } from 'hono'
import { apply, serve } from 'vike-photon/hono'
function startServer() {
const app = new Hono()
apply(app)
return serve(app)
}
export default startServer()⚠️Photon's Cloudflare integration (
@photonjs/cloudflare) currently only supports Hono and Hattip. (Support for more servers is work-in-progress.)
Where:
apply()installs the middleware of Vike and Vike extensions.serve()attaches your server to Node.js, Cloudflare, Netlify, Vercel, Deno, Bun, ...See
serve()for a list of options.
Note that:
- Vike is automatically added to your server — no need to manually integrate
renderPage(). - Some Vike extensions may also automatically add server middlewares.
- Static files are automatically served.
Deployment
Node/Bun/Deno
In production, run $ node ./dist/server/index.mjs (or Bun/Deno).
Vercel
See Deploy > Vercel.
Cloudflare
See Deploy > Cloudflare.
serve()
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 ...
})Custom pageContext
To define custom pageContext properties based on the HTTP request, you can use +onCreatePageContext.server.js with pageContext.runtime, for example:
// pages/+onCreatePageContext.server.js
// Environment: server
// This hook is called upon new incoming HTTP requests
export async function onCreatePageContext(pageContext) {
pageContext.user = pageContext.runtime.req.user
}// pages/+onCreatePageContext.server.ts
// Environment: server
import type { PageContextServer } from 'vike/types'
// This hook is called upon new incoming HTTP requests
export async function onCreatePageContext(pageContext: PageContextServer) {
pageContext.user = pageContext.runtime.req.user
}
declare global {
namespace Vike {
interface Photon {
// 👉 Pick your server (for correct pageContext.runtime type)
server: 'hono' // | 'express' | 'fastify' | 'hattip' | 'srvx' | 'elysia' | 'h3'
}
}
}See also: Integration > Authentication.
The
+onCreatePageContext.server.jshook above isn't really needed: you can simply accesspageContext.runtime.req.userinstead — it's just a convenience to setpageContext.useras an alias forpageContext.runtime.req.user
Standalone
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).
⚠️This feature is experimental and may break upon any version update.
If the production code built with
standalone: truefails to run with errors likeENOENT: no such file or directory, then disable standalone mode or replace the npm package throwing the error with another npm package. (The issue is that some npm package have dependencies that aren't explicit and, consequently, cannot be statically analyzed and cannot be included in the standalone bundle.)
// +config.js
import vikePhoton from 'vike-photon/config'
export const config = {
// ...
extends: [vikePhoton],
photon: {
server: {
id: 'server/index.js',
standalone: true
}
}
}// +config.ts
import type { Config } from 'vike/types'
import vikePhoton from 'vike-photon/config'
export const config = {
// ...
extends: [vikePhoton],
photon: {
server: {
id: 'server/index.ts',
standalone: true
}
}
} satisfies ConfigOptions:
export const config = {
// ...
extends: [vikePhoton],
photon: {
server: {
id: 'server/index.js',
standalone: {
esbuild: {
minify: true
// ... or any other esbuild option
}
}
}
}
}
import type { Config } from 'vike/types'
export const config = {
// ...
extends: [vikePhoton],
photon: {
server: {
id: 'server/index.ts',
standalone: {
esbuild: {
minify: true,
// ... or any other esbuild option
}
}
}
}
} satisfies ConfigInstead of using
standalone: true, we recommend tools such aspnpm deploy --prod. This provides better control over packed files and ensures greater compatibility.
HMR
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-photonsometimes 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:
// pages/+config.js
import vikePhoton from 'vike-photon/config'
export default {
extends: [vikePhoton],
photon: {
// Disable compression
compress: false
// Only compress static files
compress: 'static'
}
}// pages/+config.ts
import type { Config } from 'vike/types'
import vikePhoton from 'vike-photon/config'
export default {
extends: [vikePhoton],
photon: {
// Disable compression
compress: false,
// Only compress static files
compress: 'static'
}
} satisfies ConfigStatic files
In production, Photon uses @universal-middleware/sirv to serve static files.
In development, static files are served by Vite's development server.
You can disable it, or set options:
// pages/+config.js
import vikePhoton from 'vike-photon/config'
export default {
extends: [vikePhoton],
photon: {
// Don't serve static files
static: false
// Settings
static: { maxAge: 3600, /* ... */ }
}
}// pages/+config.ts
import type { Config } from 'vike/types'
import vikePhoton from 'vike-photon/config'
export default {
extends: [vikePhoton],
photon: {
// Don't serve static files
static: false,
// Settings
static: { maxAge: 3600, /* ... */ }
}
} satisfies ConfigAll
sirvoptions are supported.
HTTPS
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 ...
},
})