@virke/nextjs-adapter
v1.1.0
Published
Fastly deployment target for Next.js via [vinext](https://github.com/unjs/vinext) (Cloudflare's Next.js on Vite reimplementation).
Readme
@virke/nextjs-adapter
Fastly deployment target for Next.js via vinext (Cloudflare's Next.js on Vite reimplementation).
Features
- Fastly Compute deployment target for vinext (Vite Environment API plugin)
- FastlyKVCacheHandler for ISR using Fastly KV Store (4-10ms warm reads)
- Static/SSR split: static assets to Object Storage (free VCL), SSR on Compute
- Zero cold starts on Fastly's global edge network
- Service chaining to virke-api for DB/auth
- Traffic-aware pre-rendering using Fastly log data (future)
Architecture
Build & Deploy Flow
Next.js App
↓
vinext (Vite compilation)
↓
Fastly Deployment Target
↓
├─→ Static Assets → Object Storage → VCL serving (free)
└─→ SSR/RSC Routes → Fastly Compute Wasm → Edge executionRuntime Architecture
Request
↓
Fastly Edge
├─→ Static assets? → Object Storage (VCL, <1ms)
└─→ Dynamic route? → Compute@Edge
├─→ ISR cached? → KV Store (4-10ms)
├─→ SSR render → vinext runtime
└─→ DB/Auth needed? → Service chain to virke-apiInstallation
bun add @virke/nextjs-adapter vinextUsage
1. Configure Vite
Create or update vite.config.ts:
import { defineConfig } from 'vite'
import { vinext } from 'vinext'
import { fastlyDeploymentTarget } from '@virke/nextjs-adapter/deployment-target'
export default defineConfig({
plugins: [
vinext(),
fastlyDeploymentTarget({
serviceId: process.env.FASTLY_SERVICE_ID,
apiToken: process.env.FASTLY_API_TOKEN,
kvStore: 'next_cache',
staticBucket: 'virke-sites',
staticPrefix: '/my-app/v1'
})
]
})2. Configure ISR Cache Handler
Create app/cache-config.ts:
import { setCacheHandler } from 'next/cache'
import { FastlyKVCacheHandler } from '@virke/nextjs-adapter/cache-handler'
// Initialize ISR cache handler for Fastly KV Store
setCacheHandler(new FastlyKVCacheHandler({
kvStore: 'next_cache',
keyPrefix: 'isr_',
defaultTtl: 31536000 // 1 year
}))Import this file in your root layout or _app.tsx:
import './cache-config'3. Configure Fastly Resources
Add to fastly.toml:
name = "my-nextjs-app"
description = "Next.js app on Fastly via vinext"
authors = ["[email protected]"]
language = "javascript"
[local_server]
[local_server.kv_stores]
[local_server.kv_stores.next_cache]
[setup.kv_stores.next_cache]
[setup.kv_stores.next_cache.link]
resource_id = "your-kv-store-id"4. Environment Variables
Create .env:
FASTLY_SERVICE_ID=your-service-id
FASTLY_API_TOKEN=your-api-token
VIRKE_STATIC_PREFIX=/my-app/v15. Build & Deploy
bun run buildThe deployment target will:
- Build your Next.js app with vinext
- Split static assets from SSR code
- Upload static assets to Object Storage
- Compile SSR code to Fastly Compute Wasm
- Deploy to your Fastly service
API Reference
fastlyDeploymentTarget(config)
Vite plugin for deploying vinext output to Fastly.
Config:
interface FastlyDeploymentConfig {
serviceId: string // Fastly Compute service ID
apiToken?: string // Fastly API token (optional for build-only)
kvStore?: string // KV Store for ISR cache
staticBucket?: string // Object Storage bucket for static assets
staticBackend?: string // Backend name (default: "object_storage")
staticPrefix?: string // Path prefix (e.g., "/my-app/v1")
apiBackend?: string // virke-api backend (default: "virke_api")
}FastlyKVCacheHandler
ISR cache handler using Fastly KV Store.
Methods:
class FastlyKVCacheHandler {
constructor(config: CacheHandlerConfig)
async get(key: string): Promise<CacheEntry | null>
async set(key: string, value: CacheEntry, ttl?: number): Promise<void>
async delete(key: string): Promise<void>
async revalidateTag(tag: string): Promise<void>
async tag(key: string, tags: string[]): Promise<void>
async stats(): Promise<{ entries: number; prefixes: string[] }>
}createFastlyServer(config)
Create a Fastly Compute server for Next.js SSR/RSC routes.
Config:
interface FastlyServerConfig {
kvStore: string // KV Store for ISR
staticBackend?: string // Object Storage backend
staticPrefix?: string // Static asset prefix
apiBackend?: string // virke-api backend
vinextHandler: (request: Request) => Promise<Response> // vinext SSR handler
}Returns:
interface FastlyServer {
handle(request: Request): Promise<Response>
}Performance
ISR Cache (KV Store)
| Operation | Latency | Notes | |-----------|---------|-------| | Warm read | 4-10ms | Cache hit | | Cold read | ~50ms | Includes deserialization | | Write | ~450ms | Small entries (<10KB) | | Revalidate | ~100ms | Surrogate key purge + KV delete |
Static Assets (Object Storage + VCL)
| Operation | Latency | Notes | |-----------|---------|-------| | CDN hit | <1ms | VCL serving from POP cache | | CDN miss | 10-30ms | Fetch from Object Storage |
SSR Rendering (Compute@Edge)
| Operation | Latency | Notes | |-----------|---------|-------| | Simple page | 10-30ms | Static props, no DB | | DB query | 20-50ms | Service chain to virke-api | | Complex page | 50-100ms | Multiple DB queries |
Fastly Resources
Required Services
- Compute service: Runs SSR/RSC code (StarlingMonkey Wasm runtime)
- VCL service (optional): Serves static assets from Object Storage
Required Resources
- KV Store: ISR cache (
next_cache) - Object Storage bucket: Static assets (
virke-sites) - Backend (Compute→Object Storage):
object_storage - Backend (Compute→virke-api):
virke_api(for DB/auth)
Resource Configuration
KV Store:
fastly kv-store create --name=next_cache
fastly resource-link create --version=latest --resource-id=<store-id>Object Storage:
# Use Fastly control panel or AWS CLI
aws s3 mb s3://virke-sites --endpoint-url=https://eu-central.object.fastlystorage.appService Chaining:
[setup.backends.virke_api]
address = "virke-api.edgecompute.app"
port = 443
override_host = "virke-api.edgecompute.app"Examples
Basic Next.js App
// app/page.tsx
export const revalidate = 60 // ISR: revalidate every 60 seconds
export default async function Home() {
const data = await fetch('https://api.example.com/data')
const json = await data.json()
return <div>Hello {json.name}</div>
}With Virke DB
// app/users/page.tsx
import { virkeEnv } from '@virke/runtime'
const env = virkeEnv({ db: 'my-db' })
export default async function Users() {
const users = await env.db.query('SELECT * FROM users')
return (
<ul>
{users.rows.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}Tag-based Revalidation
// app/posts/[id]/page.tsx
import { unstable_cache } from 'next/cache'
export default async function Post({ params }: { params: { id: string } }) {
const post = await unstable_cache(
() => fetchPost(params.id),
['post', params.id],
{ tags: [`post-${params.id}`] }
)()
return <article>{post.content}</article>
}
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string) {
// ... update post in DB
await revalidateTag(`post-${id}`)
}Limitations & Roadmap
Current Limitations
- vinext is experimental: API may change frequently
- No local dev yet: Requires Fastly Compute for testing
- Build artifacts: Manual Wasm compilation (will be automated)
- Traffic-aware pre-rendering: Not yet implemented
Roadmap
- [ ] Implement actual Wasm compilation in deployment target
- [ ] Add S3 upload logic for static assets
- [ ] Integrate with @virke/fastly-client for service deployments
- [ ] Traffic-aware pre-rendering from Fastly logs
- [ ] Local dev server with Fastly CLI
- [ ] Edge middleware support
- [ ] Image optimization with Fastly Image Optimizer
Comparison to Vercel
| Feature | Vercel | Virke + vinext | |---------|--------|----------------| | SSR latency | 50-200ms | 10-100ms | | ISR cache | Edge Network | KV Store (4-10ms) | | Static serving | Edge Network | Object Storage + VCL (<1ms) | | Cold starts | 50-500ms | 0ms (Wasm) | | Global POPs | ~100 | ~70 (Fastly) | | Cost | $20/mo Pro | $0-50/mo (usage-based) |
Related Packages
@virke/runtime— Virke runtime bindings (DB, KV, OS3)@virke/cli— Virke CLI for deployments@virke/sdk— Virke API client SDK
License
Apache-2.0
Contributing
See CONTRIBUTING.md for guidelines.
