Skip to content
getknext-devPublic

About

The scale-to-zero Next.js adapter for Knative and Kubernetes — a real Next.js Deployment Adapter, not a PaaS.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

3,781 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

knext: the scale-to-zero Next.js adapter for Knative

knext deploys an existing Next.js app to Kubernetes as a Knative Service, built on the official Next.js Deployment Adapter API — so your app scales to zero when idle and scales back up on the next request, on any Kubernetes cluster with Knative Serving installed.

knext is not a general-purpose PaaS. It does not run arbitrary containers or frameworks; it is a focused, Next.js-specific deployment tool.

The credential (in progress): knext runs the official Next.js compatibility test suite across four runtime x build-system combinations (Node/Bun x Turbopack/Webpack). Its credential is 14 consecutive green independent runs of that suite per combination (three scheduled runs a day, at least two hours apart), against a frozen release reference — that window is in progress, not complete, and knext does not claim verified status until it is. v1.0 is the semver-stable release; it ships ahead of the credential, from the same bytes the credential runs execute. See docs/compat-matrix.md for the current, evidence-gated status of each cell — do not rely on a number quoted here, which would go stale; that file is the live source of truth. See docs/release/v1.0.0.md for the credential's status and which parts of knext the v1.0 contract covers (stability tiers).

Quick Start

npx @getknext/core create my-app
cd my-app
# fill in registry, bucket, and database settings in knext.config.ts
npx @getknext/core deploy

That builds your app with the official Next.js adapter, pushes the image, and applies a NextApp custom resource that the knext operator reconciles into a Knative Service. See Quick Start below for the full walkthrough, including cluster prerequisites.


Table of Contents


Why Knative?

Knative is a Kubernetes-based platform that provides serverless capabilities without lock-in to any specific cloud provider. Knative runs on any Kubernetes cluster (GKE, EKS, AKS, or on-premise).

Benefits

Feature Knative (via knext)
Portability Any Kubernetes cluster (portable by design; verified end-to-end on GKE and OKE, kind in CI, and the core operator/CLI deploy path validated end-to-end on EKS; AKS and OpenShift are not yet live-validated)
Scale-to-Zero Idle apps run zero pods; the platform brings a pod back up on the next request
Autoscaling Configurable (KPA/HPA)
Cold starts Optimized for scale-to-zero — the build pipeline includes bytecode compile caching to remove V8 compilation work from a cold wake. End-to-end wake time is dominated by cluster scheduling and is environment-dependent; see docs/benchmarks/scale-to-zero-oke.md and the tuning cold start guide for measured numbers and their conditions — we do not publish a single number as a universal guarantee
Container control Full Docker/OCI image access
Networking Full Kubernetes networking; a default-on NetworkPolicy is reconciled per app, with enforcement depending on your cluster's CNI
Cost model Per-pod-second, not per-invocation

Use cases

  • Multi-cloud deployments requiring platform portability
  • On-premise or air-gapped environments
  • Hybrid architectures with existing Kubernetes workloads
  • Cost optimization for spiky or low-traffic apps that don't need an always-on pod

What knext does not do (yet)

  • No global edge network or CDN — knext deploys to the cluster(s) you run.
  • No edge middleware or Partial Prerendering (PPR) — partly gated on upstream Next.js, not solely a knext gap.
  • The compiled single-executable ("vinext") build target is Beta, stays Beta in every release, and is outside the compatibility credential's scope.

How It Works: Next.js Adapter

kn-next integrates via the official Next.js Adapter API (NextAdapter), registered through the top-level adapterPath config (Next.js 16.2+; on 16.0.x–16.1.x it lives under experimental.adapterPath). A standard next build with output: 'standalone' produces a self-contained Node server; the adapter hooks into the build to wire Knative-specific behavior — no custom compiler, no fork of Next.js.

Build Pipeline

flowchart LR
    subgraph Input
        A["Next.js App<br/>(App Router)"]
    end
    
    subgraph Build
        B["next build<br/>output: 'standalone'<br/>+ NextAdapter"]
    end
    
    subgraph Output
        C["Standalone Server<br/>(distroless Node)"]
    end
    
    A --> B --> C
    
    C --> D["Static Assets<br/>→ CDN/GCS"]
    C --> E["Server (server.js)<br/>→ Docker"]
    C --> F["Adapters<br/>(GCS/S3/Redis)"]

    style A fill:#3b82f6,color:#fff
    style B fill:#8b5cf6,color:#fff
    style C fill:#10b981,color:#fff
    style D fill:#f59e0b,color:#fff
    style E fill:#f59e0b,color:#fff
    style F fill:#f59e0b,color:#fff
Loading

What the Adapter Does

  1. modifyConfig → defaults output to 'standalone' (an explicit output: 'export' is left alone and the CLI refuses it — a static export is not a server deployment, and knext deploys a Next.js server) and injects the Redis cacheHandler at build time
  2. onBuildComplete → uploads staticFiles + prerenders to cloud storage, keyed by buildId
  3. Standalone server → next build emits a self-contained server.js that runs on plain Node 20+ (and Bun via node:http compat) — no bundler-specific runtime
  4. Cold-start bytecode caching → NODE_COMPILE_CACHE persists the V8 code cache so scaled-from-zero pods skip re-compilation (the mechanism behind Vercel Fluid)

Features

  • ✅ Official Next.js Adapter API – next build standalone, no fork, runs on Node 20+ and Bun
  • ✅ V8 Bytecode Caching – the compile cache is populated at build time and baked into the image (Vercel-Fluid-style), so it is present from the very first cold pod on a stock cluster. Nothing to enable, no volume, no storage class, and no limit on how wide the app scales
  • ✅ Fluid Compute – Scale-to-zero, high concurrency, auto-scaling
  • ✅ Distributed Caching – Redis-backed caching with automatic tag invalidation
  • 🟡 Portable by design – verified end-to-end on GKE and OKE (kind in CI), and the core operator/CLI deploy path is also verified end-to-end on EKS (knext deploy → NextApp CR → operator reconciles → Knative Service → live 200 route). The EKS data-plane steps (S3 asset upload, ISR/tag invalidation) are documented but not each validated there; AKS and OpenShift are not yet live-validated. See Multi-Cloud Portability and the docs-site support matrix.
  • ✅ Cache Monitoring – Built-in cache event dashboard
  • ✅ Single-Command Deploy – Automated build, push, and deploy
  • ✅ Monorepo Ready – Turborepo for efficient builds
  • ✅ Kubernetes Operator – Declarative NextApp CRD for GitOps-style deployment

What Next.js features does knext support? See the evidence-gated compatibility matrix — every ✅ is backed by a red-on-fail check, and a guard test fails CI on any overclaim. knext's dispatched runs of the official Next.js deploy-test suite pass on all four default cells (Node/Bun x Turbopack/Webpack) — 1101 of 1101 tests at Next.js v16.3.8 — with a small, documented set of exclusions. That is evidence, not the credential, which is still in progress (see above). The matrix has the exact scope and the run evidence.


Performance Benchmarks

Benchmarks measured on Knative Serving with scale-to-zero (minScale: 0) on GKE. Cold start speed is achieved through Knative resource caching and V8 bytecode caching (a compile cache baked into the image at build time).

Cold Start Performance (Scale from Zero)

With minScale: 0, pods terminate after 10 seconds of inactivity and must be provisioned fresh on next request. The figures below are from a single early GKE run and are not representative — cold start is scheduling-dominated and environment-dependent. Our multi-run OKE measurements on a quiet two-node cluster, with a light app and the image already on the node, gave medians of about 1.9 to 2.4 s from the first request to a full page (scheduling-bound; individual wakes can take several times the median; your numbers will differ) — treat that as the honest figure and these as one favorable data point. See scale-to-zero & cold starts for the method and the table, and benchmarks for the older multi-run record.

Metric Value (single early GKE run — not representative)
Time to First Byte (TTFB) 0.66s
Total Response Time 0.92s
Pod Provisioning Container goes from Pending → Running in ~1s

Warm Start Performance

Metric Value
Time to First Byte (TTFB) 0.58s
Total Response Time 0.80s

Load Testing (100K Requests)

seq 1 100000 | xargs -n1 -P100 -I {} curl -s -o /dev/null -w "%{time_total}\n" \
  "http://file-manager.default.136.111.227.195.sslip.io/audit"
Metric Value
Total Requests 100,000
Concurrency 100 parallel workers
Average Response Time 0.521s
Requests/Second (RPS) ~192 req/s
Per-Pod RPS ~96 req/s (with maxScale: 2)
Total Test Duration ~521s (~8.7 min)
Error Rate 0% (all requests successful)

How Cold Starts Are Optimized

The Dockerfile uses a 2-stage build producing a small Alpine image that ships one executable:

  1. Build Stage – oven/bun:1.4.2-alpine runs vite build (Vite/rolldown → a Nitro .output), then compiles that output into a single self-contained binary with bun build --compile --minify --bytecode. sharp's native addon is staged beside it into native/, with a sha256 manifest derived from the lockfile.
  2. Runtime Stage – alpine:3.22 with only libstdc++ and libgcc, running the compiled binary directly. No Node, no npm, no package manager, and no bytecode cache to mount.

Bytecode is compiled into the binary at build time, as one unit, so every pod — including the very first cold start — starts from precompiled bytecode rather than parsing and JIT-compiling JavaScript. It needs no volume, no PVC, and no cluster feature flags.

(This section previously described a node:22 builder emitting output: 'standalone' and a distroless Node runtime reading a NODE_COMPILE_CACHE directory. That is the older shape and no longer what this Dockerfile builds.)

What that buys, measured: on a fresh local process with the binary already page-cached, this vinext executable's process boot measured 61 ms median against the Node standalone's 884 ms, at higher steady-state throughput (1103 vs 630 req/s) — both local-machine numbers, not a cluster figure. A truly cold local spawn is closer to 1.8 s. On a real Knative cluster (OKE, N=7 paired cold cycles), scale-from-zero was a tie: node-standalone 3610 ms vs vinext single-exec 3401 ms median — cold start there is dominated by Knative activation/scheduling/container-create, not process boot, so it does not differentiate the two build targets. The per-file bytecode pass that used to run for runtime: bun standalone builds is retired: it traded throughput for cold start (537 req/s), and the whole-bundle compile wins both axes.

Running the legacy standalone shape on Bun? One mechanism still applies:

  1. Runtime transpiler cache: Bun's transpiler cache is derived from the image-baked NODE_COMPILE_CACHE path. Bun persists the transpiled source of large modules (roughly 50 KB and up) so a pod skips re-transpiling them — about 20% faster alone, and it picks up whatever the build-time pass didn't cover. Per-pod only: it no longer survives scale-to-zero, because the persistent volume that used to carry it across cold starts has been removed. Fail-open: if the cache directory is missing or unwritable, Bun simply serves without it. Set BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 to opt out.

Quick Start

New to knext? Follow the step-by-step Quickstart guide — from an empty cluster (operator install included) to a deployed, scale-to-zero app.

Prerequisites

  • Node.js 22.18+ (24 LTS recommended) or Bun 1.2+
  • Docker with buildx
  • kubectl configured for your cluster
  • Cloud storage bucket (GCS/S3/Azure/MinIO)
  • Redis instance (optional, for cache invalidation)

1. Install Dependencies

bun install

2. Configure Your App

Create knext.config.ts in your app directory:

import type { KnativeNextConfig } from '@getknext/core';

const config: KnativeNextConfig = {
  name: 'my-app',
  
  // Storage for static assets
  storage: {
    provider: 'gcs',           // 'gcs' | 's3' | 'azure' | 'minio'
    bucket: 'my-assets-bucket',
    publicUrl: 'https://storage.googleapis.com/my-assets-bucket',
  },
  
  // Cache for fast tag-based invalidation
  cache: {
    provider: 'redis',
    url: 'redis://redis.default.svc.cluster.local.:6379',
    keyPrefix: 'my-app',
  },
  
  // Container registry
  registry: 'gcr.io/my-project',
  
  // Knative autoscaling
  scaling: {
    minScale: 1,   // Keep 1 pod always running (no cold starts)
    maxScale: 10,  // Scale up to 10 pods
  },
};

export default config;

3. Deploy

# Using the deploy script
./deploy.sh

# Or using the CLI
npx @getknext/core deploy

This single command:

  1. Runs next build (output: 'standalone') with the knext adapter
  2. Syncs static assets + prerenders to cloud storage (keyed by buildId)
  3. Builds & pushes the runtime image, pinned by digest
  4. Applies a NextApp custom resource — the only thing the CLI writes to your cluster
  5. The operator reconciles it into a scale-to-zero Knative Service

Configuration Reference

Complete Configuration Schema

interface KnativeNextConfig {
  // Required: Application name (used for K8s resources)
  name: string;
  
  // Required: Container registry URL
  registry: string;
  
  // Required: Storage configuration
  storage: StorageConfig;
  
  // Optional: Cache configuration (default: none)
  cache?: CacheConfig;
  
  // Optional: ISR revalidation queue (default: none)
  queue?: QueueConfig;
  
  // Optional: Deploy infrastructure services alongside app
  infrastructure?: InfrastructureConfig;
  
  // Optional: Knative autoscaling settings
  scaling?: ScalingConfig;

  // Optional: bring-your-own database binding (no provisioning)
  database?: DatabaseConfig;
}

Storage Providers

Configure where static assets are stored:

// Google Cloud Storage
storage: {
  provider: 'gcs',
  bucket: 'my-bucket',
  publicUrl: 'https://storage.googleapis.com/my-bucket',
}

// AWS S3
storage: {
  provider: 's3',
  bucket: 'my-bucket',
  region: 'us-east-1',
  publicUrl: 'https://my-bucket.s3.amazonaws.com',
}

// Azure Blob Storage
storage: {
  provider: 'azure',
  bucket: 'my-container',  // container name
  region: 'eastus',
}

// MinIO (S3-compatible, self-hosted)
storage: {
  provider: 'minio',
  bucket: 'my-bucket',
  endpoint: 'http://minio.default.svc.cluster.local:9000',
  accessKey: 'minioadmin',
  secretKey: 'minioadmin',
}

Cache Providers

Configure distributed cache for tag-based invalidation:

// Redis (recommended)
cache: {
  provider: 'redis',      // the only implemented cache provider
  url: 'redis://redis:6379',
  keyPrefix: 'my-app',    // Optional namespace
  tls: true,              // Enable TLS
}

Queue Providers (ISR Revalidation)

Configure background revalidation queue:

// Kafka (Knative Eventing compatible)
queue: {
  provider: 'kafka',
  brokerUrl: 'kafka:9092',
  topic: 'my-app-revalidation',
}

// No queue (synchronous revalidation)
queue: {
  provider: 'none',
}

Infrastructure Services

Auto-deploy PostgreSQL, Redis, or MinIO alongside your app:

infrastructure: {
  postgres: {
    enabled: true,
    version: '16',
    storage: '10Gi',
  },
  redis: {
    enabled: true,
    version: '7',
  },
  minio: {
    enabled: true,
    storage: '20Gi',
    accessKey: 'admin',
    secretKey: 'secretpassword',
  },
}

Scaling Configuration

scaling: {
  minScale: 0,   // Scale to zero (save costs, but cold starts)
  maxScale: 10,  // Maximum pods
}

// For low-latency requirements
scaling: {
  minScale: 1,   // Always keep 1 pod running
}

Beyond minScale/maxScale/cpuRequest/memoryRequest/cpuLimit/memoryLimit, scaling also exposes the Knative autoscaling knobs the NextApp resource supports. All are optional; leaving one unset omits it from the emitted NextApp resource and the operator's own default applies unchanged:

scaling: {
  // Concurrent requests per pod before Knative adds another pod.
  // Operator default: 20.
  containerConcurrency: 20,

  // Per-pod DATABASE_URL connection-pool maximum. When set, the operator
  // enforces `maxScale × poolMax ≤ 80` (the app connection budget) and
  // caps the runtime pg pool to match.
  poolMax: 5,

  // Scheduled warm-floor windows: during each window the app is
  // pre-warmed to a floor of `replicas` pods so a known traffic peak
  // doesn't pay a cold start. Cron expressions are evaluated in `timezone`
  // (default "UTC"). Outside every window the floor reverts to `minScale`.
  warmSchedule: [
    { start: '0 8 * * 1-5', end: '0 20 * * 1-5', replicas: 2, timezone: 'UTC' },
  ],

  // Whether the Knative activator stays in the request path as a burst
  // buffer for an unpredicted spike. -1 = always keep it in the path
  // (max burst tolerance); >= 0 = the buffered request count before it's
  // removed. Unset = the Knative cluster default (200) applies.
  targetBurstCapacity: -1,

  // How fast the Knative Pod Autoscaler reacts to an unpredicted surge,
  // as a percentage of the stable window (1-100; smaller = more
  // reactive). Unset = the Knative cluster default (10%) applies.
  panicWindowPercentage: 10,

  // The overshoot (as a percentage of the steady-state target) that trips
  // panic mode (>= 110; lower = trips sooner). Unset = the Knative
  // cluster default (200%) applies.
  panicThresholdPercentage: 200,
}

Database Binding

knext is engine-agnostic and provisions no database — bring your own Postgres (or whatever engine your app uses) and bind it into knext.config.ts. database binds an existing Kubernetes Secret's connection string into the app as DATABASE_URL (and optionally a read-only replica DSN as DATABASE_URL_RO):

database: {
  // Binds an existing Secret as the app's DATABASE_URL.
  // `key` defaults to "DATABASE_URL" when omitted.
  secretRef: { name: 'storefront-db' },

  // Optional: bind a read-only DSN as DATABASE_URL_RO. Requires secretRef.
  // A single Secret carrying both keys binds with roSecretRef: { name: <same> }.
  // `key` defaults to "DATABASE_URL_RO" when omitted.
  roSecretRef: { name: 'storefront-db' },
}

Only the Secret name and key are ever written to this config file — never a connection string or credential. The DSN itself lives exclusively in the Kubernetes Secret. Omitting database entirely leaves the app's database wiring unchanged (back-compat).


Caching & Adapters

Two-Tier Cache Architecture

flowchart TB
    subgraph Storage["Cloud Storage (GCS/S3/Azure/MinIO)"]
        direction TB
        S1["Static Assets (JS/CSS)"]
        S2["Public Files"]
    end
    
    subgraph Redis["Redis (ISR & Tag Cache)"]
        direction TB
        R1["ISR page & fetch cache"]
        R2["tag:{tagName} → Set of keys"]
        R3["Fast O(1) tag-based invalidation"]
    end

    %% The two tiers are independent stores, so there is no data flow to draw
    %% between them. This invisible link exists only to fix their rank order:
    %% with no edge at all, `flowchart TB` has nothing to rank on and packs the
    %% subgraphs side by side in reverse declaration order.
    Storage ~~~ Redis

    style Storage fill:#4f46e5,color:#fff
    style Redis fill:#dc2626,color:#fff
Loading

Available Adapters

Adapter File Purpose
Node Server node-server.ts HTTP server wrapper for Knative
Bytecode Metrics bytecode-metrics.ts Prometheus metrics for the node server

Cache-Control Normalization (KNEXT_CACHE_CONTROL_NORMALIZE)

Next.js emits shared-cache directives on prerendered/ISR responses (e.g. s-maxage=2, stale-while-revalidate=…, or s-maxage=31536000 for pages that never revalidate). Those directives are meant to be consumed by a deployment platform's cache layer — they are not what a browser should see. On a knext deployment, Knative serves clients directly, so the runtime entry (@getknext/core/internal/node-server) normalizes them by default, exactly like the official reference adapter does in its serving layer:

  • Applies to GET/HEAD responses only.
  • immutable values (hashed static assets) pass through untouched.
  • Any Cache-Control containing s-maxage= becomes public, max-age=0, must-revalidate — browsers always revalidate, and ISR freshness is handled server-side by the incremental cache.
  • The pages-router fallback-shell private, no-cache, no-store, … value is normalized the same way, only when the response carries the x-nextjs-cache marker and is not a /_next/data/ request.
  • Everything else (custom headers you set in routes/middleware) is untouched.

When to disable it: if you front knext with your own CDN or shared cache that honors s-maxage (Cloudflare, CloudFront, Fastly, Varnish, …), the default normalization strips s-maxage before your CDN sees it, so the CDN stops shared-caching ISR pages. In that setup, disable normalization and let your CDN consume the origin directives:

// knext.config.ts — plain, non-secret env vars ride the NextApp resource's env field
export default {
    // ...
    env: {
        KNEXT_CACHE_CONTROL_NORMALIZE: "0",
    },
};

or directly on the NextApp resource:

spec:
  env:
    KNEXT_CACHE_CONTROL_NORMALIZE: "0"

(Secret-backed values still go through spec.secrets / the secrets config option — env is for plain configuration flags only. A Dockerfile ENV KNEXT_CACHE_CONTROL_NORMALIZE=0 also works for per-image settings.) Any value other than 0 — including unset — leaves normalization on.

Bun Keep-Alive Guard (KNEXT_BUN_KEEPALIVE_GUARD)

On Bun ≤ 1.3.x only, the runtime preloads a guard that advertises Connection: close on every response, working around a Bun node:http bug that resets reused keep-alive sockets (fixed upstream in Bun ≥ 1.4). Tradeoff: clients — including the Knative activator — open a fresh connection per request while the guard is active, trading keep-alive reuse for correctness on affected Bun versions. It self-disables on fixed Bun versions and never loads under Node; set KNEXT_BUN_KEEPALIVE_GUARD=0 to disable it (or 1 to force it on a Bun version the ceiling says is fixed).

Cache Invalidation API

# Invalidate by tag
curl -X POST http://your-app/api/cache/invalidate \
  -H "Content-Type: application/json" \
  -d '{"tag": "products"}'

# Response
{
  "success": true,
  "message": "Cache invalidated for tag: products",
  "timestamp": "2026-02-05T10:00:00.000Z"
}

Real-time Cache Events

Connect to /api/cache/events for Server-Sent Events:

const events = new EventSource('/api/cache/events');

events.onmessage = (e) => {
  const event = JSON.parse(e.data);
  // { type: 'HIT', source: 'redis', key: '/products', durationMs: 2 }
};

Multi-Cloud Deployment

Verification status: knext is portable by design. End-to-end deploys are verified on GKE and OKE (and on kind in CI), and the core deploy path is validated on EKS; the AKS example below is a design-correct configuration, not yet validated on a live cluster. See the docs-site support matrix. Before deploying to a non-GKE cluster, read Multi-Cloud Portability for the per-cloud prerequisites (ingress-class, StorageClass, LoadBalancer/gateway IP, and build-host CLI tools).

Google Cloud (GKE)

const config: KnativeNextConfig = {
  name: 'my-app',
  storage: {
    provider: 'gcs',
    bucket: 'my-gcs-bucket',
    publicUrl: 'https://storage.googleapis.com/my-gcs-bucket',
  },
  cache: {
    provider: 'redis',
    url: 'redis://redis.default.svc.cluster.local.:6379',
  },
  registry: 'gcr.io/my-project',
};

GKE Prerequisites:

# Ensure GCS write access (one of these)
gcloud container node-pools create storage-enabled \
  --scopes="gke-default,storage-full"

# Or use Workload Identity for production
gcloud iam service-accounts add-iam-policy-binding ...

AWS (EKS)

const config: KnativeNextConfig = {
  name: 'my-app',
  storage: {
    provider: 's3',
    bucket: 'my-s3-bucket',
    region: 'us-east-1',
    publicUrl: 'https://my-s3-bucket.s3.amazonaws.com',
  },
  cache: {
    provider: 'redis',
    url: 'redis://my-elasticache.xxx.cache.amazonaws.com:6379',
    tls: true,
  },
  registry: '123456789.dkr.ecr.us-east-1.amazonaws.com',
};

EKS Prerequisites:

# Configure IAM roles for S3 access
eksctl create iamserviceaccount \
  --name default \
  --namespace default \
  --cluster my-cluster \
  --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

Azure (AKS)

const config: KnativeNextConfig = {
  name: 'my-app',
  storage: {
    provider: 'azure',
    bucket: 'my-container',
    region: 'eastus',
  },
  cache: {
    provider: 'redis',
    url: 'redis://my-redis.redis.cache.windows.net:6380',
    tls: true,
  },
  registry: 'myregistry.azurecr.io',
};

On-Premise / MinIO

const config: KnativeNextConfig = {
  name: 'my-app',
  storage: {
    provider: 'minio',
    bucket: 'assets',
    endpoint: 'http://minio.storage.svc.cluster.local:9000',
    accessKey: process.env.MINIO_ACCESS_KEY,
    secretKey: process.env.MINIO_SECRET_KEY,
  },
  cache: {
    provider: 'redis',
    url: 'redis://redis.cache.svc.cluster.local.:6379',
  },
  registry: 'registry.internal.local:5000',
  infrastructure: {
    minio: { enabled: true, storage: '50Gi' },
    redis: { enabled: true },
  },
};

Architecture

See docs/ARCHITECTURE.md for detailed diagrams including:

  • System architecture (Mermaid)
  • Request/response flow
  • Cache invalidation flow
  • Deployment pipeline

High-Level Overview

flowchart TB
    A["Next.js App<br/>(App Router)"] --> B["next build<br/>(standalone + adapter)"]
    
    B --> C["Static Assets<br/>(Cloud CDN)"]
    B --> D["Server Function<br/>(Docker)"]
    B --> E["Cache Data<br/>(Storage+Redis)"]
    
    D --> F["Knative Service"]
    
    subgraph Cluster["Kubernetes Cluster"]
        F
        E
    end

    style A fill:#3b82f6,color:#fff
    style B fill:#8b5cf6,color:#fff
    style C fill:#f59e0b,color:#fff
    style D fill:#10b981,color:#fff
    style E fill:#dc2626,color:#fff
    style F fill:#06b6d4,color:#fff
    style Cluster fill:#1e293b,color:#fff
Loading

Project Structure

├── apps/
│   └── file-manager/           # Example Next.js 16 app
│       ├── knext.config.ts   # App configuration
│       ├── deploy.sh           # Deployment script
│       └── src/app/            # App Router pages
│
├── packages/
│   ├── kn-next/                # Core framework package
│   │   ├── src/adapters/       # GCS, Redis, Kafka adapters
│   │   ├── src/generators/     # Manifest generators
│   │   └── src/cli/            # CLI tools (build, deploy)
│   └── lib/                    # Shared utilities
│
└── docs/
    └── ARCHITECTURE.md         # Detailed architecture

File Manager Demo

The example app demonstrates all framework capabilities:

  • File upload/download with MinIO/GCS
  • PostgreSQL metadata storage
  • Real-time cache monitoring
  • Paginated audit logs with infinite scroll
  • Tag-based cache invalidation
  • Cache testing playground with various strategies

Demo Pages

Page URL Description
Home / File listing
Dashboard /dashboard Statistics
Audit Logs /audit Activity log
Cache Monitor /cache Real-time events
Cache Tests /cache-tests Testing playground

Cache Testing Playground

Test URL Strategy
Time-based /cache-tests/time-based revalidate: 60
On-demand /cache-tests/on-demand revalidateTag()
Fetch cache /cache-tests/fetch-cache force-cache
Parallel /cache-tests/parallel Concurrent fetches
Nested /cache-tests/nested Layout caching
Dynamic/Static /cache-tests/dynamic-static Rendering modes

Development

Local Development

cd apps/file-manager
bun run dev

Build & Deploy

# Full deployment with defaults
npx @getknext/core deploy

# Or step-by-step
npx @getknext/core build       # next build (standalone) + adapter
npx @getknext/core deploy      # Deploy to cluster
npx @getknext/core cleanup     # Remove from cluster

CLI Reference

npx @getknext/core deploy [options]
Option Short Description
--registry <url> -r Override container registry
--bucket <name> -b Override storage bucket
--tag <tag> -t Image tag (default: timestamp)
--namespace <ns> -n Kubernetes namespace (default: default)
--skip-build Skip the next build step
--skip-upload Skip asset upload to storage
--skip-infra Skip infrastructure deployment
--dry-run Generate manifests without deploying
--help -h Show help

Environment Variables (CI/CD)

These environment variables can be used instead of CLI flags:

Variable Description
KN_REGISTRY Container registry URL
KN_BUCKET Storage bucket name
KN_IMAGE_TAG Docker image tag
KN_NAMESPACE Kubernetes namespace
KN_REDIS_URL Redis connection URL (overrides config)
KN_DATABASE_URL Database connection URL (overrides config)

CI/CD Examples

GitHub Actions:

- name: Deploy to Knative
  env:
    KN_REGISTRY: gcr.io/${{ secrets.GCP_PROJECT }}
    KN_IMAGE_TAG: ${{ github.sha }}
    KN_NAMESPACE: production
    KN_REDIS_URL: ${{ secrets.REDIS_URL }}
    KN_DATABASE_URL: ${{ secrets.DATABASE_URL }}
  run: npx @getknext/core deploy

GitLab CI:

deploy:
  script:
    - npx @getknext/core deploy --tag $CI_COMMIT_SHA --namespace production
  variables:
    KN_REGISTRY: gcr.io/my-project
    KN_REDIS_URL: $REDIS_URL

Production with specific tag:

npx @getknext/core deploy --tag v1.2.3 --namespace production

Preview manifest only:

npx @getknext/core deploy --dry-run

License

Apache License 2.0 — consistent with the Go operator source headers and the Knative/Kubernetes ecosystem this project builds on. The Apache-2.0 patent grant matters for an infrastructure project intended for production adoption.

About

The scale-to-zero Next.js adapter for Knative and Kubernetes — a real Next.js Deployment Adapter, not a PaaS.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages