Edit

+server

Define +server.js to seamlessly integrate a server framework (Hono, Express, ...), or set +server: true to use Vike's built-in lightweight server.

vike-photon is deprecated — it's replaced by +server:

Get started

+server.js

Define +server.js to integrate a server:

⚠️

Because Express doesn't follow modern JavaScript standards, it currently doesn't work with the following:

  • Bun
  • Deno
  • Cloudflare

See also: srvx#132 - toFetchHandler do not work in Deno and Bun

⚠️

Because Fastify doesn't follow modern JavaScript standards, it currently doesn't work with the following:

  • Bun
  • Deno
  • Cloudflare

See also: srvx#132 - toFetchHandler do not work in Deno and Bun

If Vike doesn't have an adapter for your server, see API > vike(app) > Manual integration.

npm install hono @vikejs/hono
pnpm add hono @vikejs/hono
bun add hono @vikejs/hono
yarn add hono @vikejs/hono
npm install express @vikejs/express
pnpm add express @vikejs/express
bun add express @vikejs/express
yarn add express @vikejs/express
npm install fastify @vikejs/fastify
pnpm add fastify @vikejs/fastify
bun add fastify @vikejs/fastify
yarn add fastify @vikejs/fastify
npm install h3 @vikejs/h3
pnpm add h3 @vikejs/h3
bun add h3 @vikejs/h3
yarn add h3 @vikejs/h3
npm install elysia @vikejs/elysia
pnpm add elysia @vikejs/elysia
bun add elysia @vikejs/elysia
yarn add elysia @vikejs/elysia
// +server.js
 
import { Hono } from 'hono'
import vike from '@vikejs/hono'
 
const app = new Hono()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: app.fetch
}
// +server.ts
 
import { Hono } from 'hono'
import vike from '@vikejs/hono'
import type { Server } from 'vike/types'
 
const app = new Hono()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: app.fetch
} satisfies Server
// +server.js
 
import express from 'express'
import vike, { toFetchHandler } from '@vikejs/express'
 
const app = express()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: toFetchHandler(app)
}
// +server.ts
 
import express from 'express'
import vike, { toFetchHandler } from '@vikejs/express'
import type { Server } from 'vike/types'
 
const app = express()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: toFetchHandler(app)
} satisfies Server
// +server.js
 
import fastify from 'fastify'
import vike, { toFetchHandler } from '@vikejs/fastify'
 
const app = fastify()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
await app.ready()
 
export default {
  fetch: toFetchHandler(app.routing)
}
// +server.ts
 
import fastify from 'fastify'
import vike, { toFetchHandler } from '@vikejs/fastify'
import type { Server } from 'vike/types'
 
const app = fastify()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
await app.ready()
 
export default {
  fetch: toFetchHandler(app.routing)
} satisfies Server
// +server.js
 
import { H3 } from 'h3'
import vike from '@vikejs/h3'
 
const app = new H3()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: app.fetch
}
// +server.ts
 
import { H3 } from 'h3'
import vike from '@vikejs/h3'
import type { Server } from 'vike/types'
 
const app = new H3()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: app.fetch
} satisfies Server
// +server.js
 
import { Elysia } from 'elysia'
import vike from '@vikejs/elysia'
 
const app = new Elysia()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: app.fetch
}
// +server.ts
 
import { Elysia } from 'elysia'
import vike from '@vikejs/elysia'
import type { Server } from 'vike/types'
 
const app = new Elysia()
vike(app) // Make sure to run vike(app) before your routes
 
// ...
// Your routes
// ...
 
export default {
  fetch: app.fetch
} satisfies Server

vike(app) installs:

Make sure to run vike(app) before your routes, because the +middleware from Vike extensions should run before your routes, so that your routes can read the context set by Vike extensions (e.g. authentication).

+server: true

For simple apps, instead of creating +server.js, you can use Vike's built-in lightweight server:

// pages/+config.js
 
export default {
  server: true // Use Vike's built-in server
}
// pages/+config.ts
 
export default {
  server: true // Use Vike's built-in server
}

A +server.js that doesn't export fetch also uses Vike's built-in server, e.g. to define Settings or to export Cloudflare Durable Objects.

Deployment

See:

Settings

All settings are defined over export default { prod: { /*...*/ } }:

// +server.js
 
export default {
  // Production settings
  prod: {
    // Server port, defaults to 3000. It's ignored on Cloudflare and Vercel Edge (there
    // isn't any server in serverless deployments).
    port: 3000,
 
    hostname: 'localhost',
 
    // Called once the server is accepting connections.
    // - The `server` object comes from srvx, see https://srvx.h3.dev/guide/server
    // - This hook isn't called on Cloudflare and Vercel Edge (there isn't
    //   any server in serverless deployments).
    onReady(server) {
      console.log('Server is ready.')
    },
 
    // Called once the server is created.
    // - The `server` object comes from srvx, see https://srvx.h3.dev/guide/server
    // - This hook isn't called on Cloudflare and Vercel Edge (there isn't
    //   any server in serverless deployments).
    onCreate(server) {
      console.log('Server created.')
    },
 
    // HTTPS Options (Node.js/Bun/Deno)
    tls: {
      // 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'),
      // Optional
      passphrase: 'passphrase'
    },
 
    // Serve static files automatically
    static: true, // Default value
    // Can be disabled:
    static: false,
    // Or serve from a custom folder:
    static: './public'
  }
}
// +server.ts
 
import type { Server } from 'vike/types'
 
export default {
  // Production settings
  prod: { 
    // Server port, defaults to 3000. It's ignored on Cloudflare and Vercel Edge (there
    // isn't any server in serverless deployments).
    port: 3000,
 
    hostname: 'localhost',
 
    // Called once the server is accepting connections.
    // - The `server` object comes from srvx, see https://srvx.h3.dev/guide/server
    // - This hook isn't called on Cloudflare and Vercel Edge (there isn't
    //   any server in serverless deployments).
    onReady(server) {
      console.log('Server is ready.')
    },
 
    // Called once the server is created.
    // - The `server` object comes from srvx, see https://srvx.h3.dev/guide/server
    // - This hook isn't called on Cloudflare and Vercel Edge (there isn't
    //   any server in serverless deployments).
    onCreate(server) {
      console.log('Server created.')
    },
 
    // HTTPS Options (Node.js/Bun/Deno)
    tls: {
      // 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'),
      // Optional
      passphrase: 'passphrase'
    },
 
    // Serve static files automatically
    static: true, // Default value
    // Can be disabled:
    static: false,
    // Or serve from a custom folder:
    static: './public'
  }
} satisfies Server

The above settings only apply to production. For development, use Vite's server settings.

🚧 Server settings are separate because values usually differ between dev and prod. If you have a use case where settings share the same value in dev and prod, then reach out and we'll create server settings that apply to both dev an prod.

See Extra control for more configuration options.

pageContext.req & pageContext.res

You can use pageContext.req and pageContext.res to access the HTTP request and response objects, for example:

// pages/product/@id/+data.js
 
export async function data(pageContext) {
  // Using pageContext.req to access the logged-in user
  const { user } = pageContext.req
  // ...
}
// pages/product/@id/+data.ts
 
import type { PageContextServer } from 'vike/types'
 
export type Data = Awaited<ReturnType<typeof data>>
export async function data(pageContext: PageContextServer) {
  // Using pageContext.req to access the logged-in user
  const { user } = pageContext.req
  // ...
}
 
declare global {
  namespace Vike {
    interface Server {
      // 👉 Pick your server (for correct pageContext.req/pageContext.res type)
      server: 'hono' // | 'express' | 'fastify' | 'h3' | 'elysia'
    }
  }
}

You can use +onCreatePageContext to define custom pageContext properties, see API > pageContext > Custom.

For example, you can define pageContext.user as an alias of pageContext.req.user for convenience, see Integration > Authentication > pageContext.user.

See also:

Standalone

Use standaloner to have the build output directory (dist/) contain 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.

npm install standaloner
pnpm add standaloner
bun add standaloner
yarn add standaloner
// vite.config.js
 
import { defineConfig } from 'vite'
import standaloner from 'standaloner/vite'
 
export default defineConfig({
  plugins: [
    standaloner() 
  ]
})
// vite.config.ts
 
import { defineConfig } from 'vite'
import standaloner from 'standaloner/vite'
 
export default defineConfig({
  plugins: [
    standaloner() 
  ]
})

HMR

If you change a server file, the server code is automatically updated: the next HTTP response will be generated by the latest server code. No full server reload is required.

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

You can compress all server responses by using the compress universal middleware:

npm install @universal-middleware/compress
pnpm add @universal-middleware/compress
bun add @universal-middleware/compress
yarn add @universal-middleware/compress
// +server.js
 
import { Hono } from 'hono'
import vike from '@vikejs/hono'
import compress from '@universal-middleware/compress'
 
const app = new Hono()
vike(app) 
vike(app, [compress()]) 
// +server.ts
 
import { Hono } from 'hono'
import vike from '@vikejs/hono'
import compress from '@universal-middleware/compress'
 
const app = new Hono()
vike(app) 
vike(app, [compress()]) 
// +server.js
 
import express from 'express'
import vike, { toFetchHandler } from '@vikejs/express'
import compress from '@universal-middleware/compress'
 
const app = express()
vike(app) 
vike(app, [compress()]) 
// +server.ts
 
import express from 'express'
import vike, { toFetchHandler } from '@vikejs/express'
import compress from '@universal-middleware/compress'
 
const app = express()
vike(app) 
vike(app, [compress()]) 
// +server.js
 
import fastify from 'fastify'
import vike, { toFetchHandler } from '@vikejs/fastify'
import compress from '@universal-middleware/compress'
 
const app = fastify()
vike(app) 
vike(app, [compress()]) 
// +server.ts
 
import fastify from 'fastify'
import vike, { toFetchHandler } from '@vikejs/fastify'
import compress from '@universal-middleware/compress'
 
const app = fastify()
vike(app) 
vike(app, [compress()]) 
// +server.js
 
import { H3 } from 'h3'
import vike from '@vikejs/h3'
import compress from '@universal-middleware/compress'
 
const app = new H3()
vike(app) 
vike(app, [compress()]) 
// +server.ts
 
import { H3 } from 'h3'
import vike from '@vikejs/h3'
import compress from '@universal-middleware/compress'
 
const app = new H3()
vike(app) 
vike(app, [compress()]) 
// +server.js
 
import { Elysia } from 'elysia'
import vike from '@vikejs/elysia'
import compress from '@universal-middleware/compress'
 
const app = new Elysia()
vike(app) 
vike(app, [compress()]) 
// +server.ts
 
import { Elysia } from 'elysia'
import vike from '@vikejs/elysia'
import compress from '@universal-middleware/compress'
 
const app = new Elysia()
vike(app) 
vike(app, [compress()]) 

See +precompress to generate .br/.gz files for your static assets at build time (instead of unnecessarily compressing them on each request).

WebSockets

You can use WebSockets, for example with crossws:

  • In production, with crossws's srvx plugin (Vike's built-in production server uses srvx).
  • In development, with a Vite plugin
// server/websocket.js
 
import { defineHooks } from 'crossws'
 
export const websocket = defineHooks({
  upgrade(request) {
    // Only accept WebSocket connections to /ws
    if (new URL(request.url).pathname !== '/ws') throw new Response('Not Found', { status: 404 })
  },
  message(peer, message) {
    peer.send(`echo: ${message.text()}`)
  }
})
// server/websocket.ts
 
import { defineHooks } from 'crossws'
 
export const websocket = defineHooks({
  upgrade(request) {
    // Only accept WebSocket connections to /ws
    if (new URL(request.url).pathname !== '/ws') throw new Response('Not Found', { status: 404 })
  },
  message(peer, message) {
    peer.send(`echo: ${message.text()}`)
  }
})
// +server.js
 
import { plugin as ws } from 'crossws/server'
import { websocket } from './server/websocket'
 
// ...
 
export default {
  fetch: app.fetch,
  prod: {
    // Production
    plugins: [ws(websocket)]
  }
}
// +server.ts
 
import { plugin as ws } from 'crossws/server'
import { websocket } from './server/websocket'
import type { Server } from 'vike/types'
 
// ...
 
export default {
  fetch: app.fetch,
  prod: {
    // Production
    plugins: [ws(websocket)]
  }
} satisfies Server
// vite.config.js
 
import vike from 'vike/plugin'
import crossws from 'crossws/adapters/node'
 
export default {
  plugins: [vike(), websocketDev()]
}
 
// Development
function websocketDev() {
  return {
    name: 'websocket-dev',
    configureServer(server) {
      const ws = crossws({
        resolve: async () => {
          const { runner } = server.environments.ssr
          return (await runner.import('/server/websocket.js')).websocket
        }
      })
      server.httpServer?.on('upgrade', (req, socket, head) => {
        // Vite's HMR WebSocket
        if (req.headers['sec-websocket-protocol'] === 'vite-hmr') return
        ws.handleUpgrade(req, socket, head)
      })
    }
  }
}
// vite.config.ts
 
import vike from 'vike/plugin'
import crossws from 'crossws/adapters/node'
import type { Plugin, RunnableDevEnvironment } from 'vite'
 
export default {
  plugins: [vike(), websocketDev()]
}
 
// Development
function websocketDev(): Plugin {
  return {
    name: 'websocket-dev',
    configureServer(server) {
      const ws = crossws({
        resolve: async () => {
          const { runner } = server.environments.ssr as RunnableDevEnvironment
          return (await runner.import('/server/websocket.ts')).websocket
        }
      })
      server.httpServer?.on('upgrade', (req, socket, head) => {
        // Vite's HMR WebSocket
        if (req.headers['sec-websocket-protocol'] === 'vite-hmr') return
        ws.handleUpgrade(req, socket, head)
      })
    }
  }
}

You can also integrate WebSockets using +serverEntry.js instead of +server.js: you then don't need a Vite plugin, and you can use any WebSocket library (e.g. Socket.IO), see API > +serverEntry > Example: WebSockets.

If you have the choice, we recommend +server.js: it has a better development experience, because modifying your server code doesn't restart your server, see:

Extra control

If needed, Vike enables you to gain complete control over the server.

Vike is designed so that it never blocks you, allowing you to use:

  • Any VPS deployment (self-hosting, AWS EC2, Google Cloud, ...)
  • Any serverless/edge deployment (Cloudflare Workers, Vercel, Firebase, AWS Lambda, Google Cloud Functions, Deno Deploy, ...)
  • Any static host deployment (Cloudflare Pages, GitHub Pages, Netlify, ...)
  • Any server framework (Express, Fastify, Hono, Nitro, Hattip, Koa, Hapi, ...)
  • Any authentication strategy/tool (email/password, OAuth, Auth.js, Passport.js, Grant, Keycloak, Auth0, ...)
  • Any server utility (Docker, Nginx, PM2, ...)

+server: true => +server.js

Before (less control)
+server: true
After (more control)
+server.js
ServerVike's built-in serverYour server framework (Hono, Express, ...)
Middlewares and routesVike extensionsAlso yours (e.g. authentication, API routes, ...)

See +server: true.

+server.js => +server.js and +serverEntry.js

For full control over your server production entry (e.g. to define a custom deployment integration), you can define +serverEntry.js in addition to your +server.js.

Before (less control)
+server.js
After (more control)
+server.js and +serverEntry.js
Production server entry
(dist/server/index.mjs)
Vike's built-in production entry (srvx)Your code (+serverEntry.js), e.g. AWS Lambda's handler() or Node.js's cluster.

See also:

+server.js => +serverEntry.js

For full control over the server entry not only in production but also in development, you can define +serverEntry.js without +server.js. In that case, +serverEntry.js then represents the entire server (including the server entry) and also runs in development (not only in production).

Before (less control)
+server.js
After (more control)
+serverEntry.js
Development serverVite's development server. (Node.js with Connect.)Your server. (E.g. Bun.serve()/Deno.serve().)
Modifying server code
(development)
Applied without restarting your server (see HMR)Restarts your server

See also:

vike(app) => renderPage()

Using renderPage() instead of vike(app) gives you more control as it's lower-level (albeit less zero-config).

See: renderPage() vs vike(app).

+serverEntry.js => $ node server/index.js

Running your server yourself (e.g. $ node server/index.js) instead of using +server.js/+serverEntry.js gives you full control over how your server is built and run (albeit with a less integrated development experience).

Before (less control)
+server.js/+serverEntry.js
After (more control)
$ node server/index.js
Server codeProcessed by ViteNot processed by Vite
Server restartFine-grainedCoarse (unnecessary reloads)

See: renderPage() > Without +serverEntry.js.

See also