From ece9ce870b6cf12bc491415fea5a71209dfd1e4b Mon Sep 17 00:00:00 2001 From: Ola Adebayo Date: Thu, 5 Mar 2026 20:08:31 +0000 Subject: [PATCH 1/3] feat(examples): add simple-html, Next.js proxy, and Next.js CSR + Kubernetes examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new runnable examples covering a wider range of REP deployment patterns: - examples/simple-html: plain HTML file with SDK loaded via esm.sh, no build step, 2-stage Dockerfile producing a FROM scratch image (~6MB) - examples/nextjs-proxy: Next.js SSR app behind the gateway in proxy mode; docker-compose.yml orchestrates both services; client components use useRep() / useRepSecure() from @rep-protocol/react - examples/nextjs-csr-embedded: Next.js static export (output: 'export') served by gateway in embedded mode; full Kubernetes manifests including ConfigMap-driven feature flags with two variants — standard envFrom (rolling restart) and volume-mounted ConfigMap + file_watch hot reload (zero pod restarts); FROM scratch final image Also updates: - docs: three new example pages in examples/ with FileTree, Steps, and Aside components; sidebar updated with all three entries - README: new Examples table linking to all four examples Co-Authored-By: Claude Opus 4.6 --- README.md | 11 + docs/astro.config.mjs | 6 + .../docs/examples/nextjs-csr-kubernetes.mdx | 285 ++++++++++++++++++ .../content/docs/examples/nextjs-proxy.mdx | 190 ++++++++++++ .../src/content/docs/examples/simple-html.mdx | 140 +++++++++ examples/nextjs-csr-embedded/.dockerignore | 7 + examples/nextjs-csr-embedded/.env.example | 13 + examples/nextjs-csr-embedded/.rep.yaml | 47 +++ examples/nextjs-csr-embedded/Dockerfile | 65 ++++ .../k8s/configmap-envfile.yaml | 31 ++ .../nextjs-csr-embedded/k8s/configmap.yaml | 41 +++ .../nextjs-csr-embedded/k8s/deployment.yaml | 129 ++++++++ examples/nextjs-csr-embedded/k8s/secret.yaml | 25 ++ examples/nextjs-csr-embedded/k8s/service.yaml | 39 +++ examples/nextjs-csr-embedded/next.config.ts | 13 + examples/nextjs-csr-embedded/package.json | 26 ++ .../nextjs-csr-embedded/src/app/layout.tsx | 13 + examples/nextjs-csr-embedded/src/app/page.tsx | 23 ++ .../src/components/ConfigPanel.tsx | 49 +++ .../src/components/FeatureFlags.tsx | 36 +++ examples/nextjs-csr-embedded/tsconfig.json | 21 ++ examples/nextjs-proxy/.dockerignore | 7 + examples/nextjs-proxy/.env.example | 13 + examples/nextjs-proxy/.rep.yaml | 40 +++ examples/nextjs-proxy/Dockerfile | 40 +++ examples/nextjs-proxy/docker-compose.yml | 52 ++++ examples/nextjs-proxy/next.config.ts | 12 + examples/nextjs-proxy/package.json | 26 ++ examples/nextjs-proxy/src/app/layout.tsx | 13 + examples/nextjs-proxy/src/app/page.tsx | 18 ++ .../src/components/EnvDisplay.tsx | 58 ++++ examples/nextjs-proxy/tsconfig.json | 21 ++ examples/simple-html/.env.example | 12 + examples/simple-html/.rep.yaml | 45 +++ examples/simple-html/Dockerfile | 51 ++++ examples/simple-html/index.html | 104 +++++++ 36 files changed, 1722 insertions(+) create mode 100644 docs/src/content/docs/examples/nextjs-csr-kubernetes.mdx create mode 100644 docs/src/content/docs/examples/nextjs-proxy.mdx create mode 100644 docs/src/content/docs/examples/simple-html.mdx create mode 100644 examples/nextjs-csr-embedded/.dockerignore create mode 100644 examples/nextjs-csr-embedded/.env.example create mode 100644 examples/nextjs-csr-embedded/.rep.yaml create mode 100644 examples/nextjs-csr-embedded/Dockerfile create mode 100644 examples/nextjs-csr-embedded/k8s/configmap-envfile.yaml create mode 100644 examples/nextjs-csr-embedded/k8s/configmap.yaml create mode 100644 examples/nextjs-csr-embedded/k8s/deployment.yaml create mode 100644 examples/nextjs-csr-embedded/k8s/secret.yaml create mode 100644 examples/nextjs-csr-embedded/k8s/service.yaml create mode 100644 examples/nextjs-csr-embedded/next.config.ts create mode 100644 examples/nextjs-csr-embedded/package.json create mode 100644 examples/nextjs-csr-embedded/src/app/layout.tsx create mode 100644 examples/nextjs-csr-embedded/src/app/page.tsx create mode 100644 examples/nextjs-csr-embedded/src/components/ConfigPanel.tsx create mode 100644 examples/nextjs-csr-embedded/src/components/FeatureFlags.tsx create mode 100644 examples/nextjs-csr-embedded/tsconfig.json create mode 100644 examples/nextjs-proxy/.dockerignore create mode 100644 examples/nextjs-proxy/.env.example create mode 100644 examples/nextjs-proxy/.rep.yaml create mode 100644 examples/nextjs-proxy/Dockerfile create mode 100644 examples/nextjs-proxy/docker-compose.yml create mode 100644 examples/nextjs-proxy/next.config.ts create mode 100644 examples/nextjs-proxy/package.json create mode 100644 examples/nextjs-proxy/src/app/layout.tsx create mode 100644 examples/nextjs-proxy/src/app/page.tsx create mode 100644 examples/nextjs-proxy/src/components/EnvDisplay.tsx create mode 100644 examples/nextjs-proxy/tsconfig.json create mode 100644 examples/simple-html/.env.example create mode 100644 examples/simple-html/.rep.yaml create mode 100644 examples/simple-html/Dockerfile create mode 100644 examples/simple-html/index.html diff --git a/README.md b/README.md index 66fc26d..936ae84 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,17 @@ REP is a formal, open specification — not just a tool. --- +## Examples + +| Example | Pattern | Description | +|---|---|---| +| [`examples/todo-react/`](examples/todo-react/) | React + embedded | Full React todo app — `useRep()`, `useRepSecure()`, hot reload, `FROM scratch` Docker image | +| [`examples/simple-html/`](examples/simple-html/) | Plain HTML + embedded | Single HTML file, SDK via esm.sh, no build step, `FROM scratch` image | +| [`examples/nextjs-proxy/`](examples/nextjs-proxy/) | Next.js SSR + proxy | Next.js server behind the gateway in proxy mode; Docker Compose two-service setup | +| [`examples/nextjs-csr-embedded/`](examples/nextjs-csr-embedded/) | Next.js CSR + embedded + Kubernetes | Static export served by gateway; ConfigMap-driven feature flags with hot-reload variant (zero pod restarts) | + +--- + ## Contributing We welcome contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, commit conventions, and the release process. diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index cfc611f..a739118 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -144,6 +144,12 @@ export default defineConfig({ slug: 'examples/quick-no-manifest', }, { label: 'Todo App (React)', slug: 'examples/todo-react' }, + { label: 'Simple HTML (ESM.sh)', slug: 'examples/simple-html' }, + { label: 'Next.js — Proxy Mode', slug: 'examples/nextjs-proxy' }, + { + label: 'Next.js CSR + Kubernetes', + slug: 'examples/nextjs-csr-kubernetes', + }, ], }, { diff --git a/docs/src/content/docs/examples/nextjs-csr-kubernetes.mdx b/docs/src/content/docs/examples/nextjs-csr-kubernetes.mdx new file mode 100644 index 0000000..f37cc01 --- /dev/null +++ b/docs/src/content/docs/examples/nextjs-csr-kubernetes.mdx @@ -0,0 +1,285 @@ +--- +title: Next.js CSR — Embedded Mode + Kubernetes +description: Export Next.js as a static site, serve it with the REP gateway in embedded mode, and manage feature flags dynamically via a Kubernetes ConfigMap. Includes a hot-reload variant with zero pod restarts. +--- + +import { Aside, FileTree, Steps } from '@astrojs/starlight/components'; + +Next.js with `output: 'export'` produces a fully static site. The REP gateway serves the output in **embedded mode** — no Node.js server needed in production. A Kubernetes ConfigMap holds all runtime config. Update it to change feature flags across all pods without rebuilding the image. + +Source code at [`examples/nextjs-csr-embedded/`](https://github.com/ruachtech/rep/tree/main/examples/nextjs-csr-embedded). + +## Architecture + +``` +Browser → REP Gateway :8080 (embedded mode) + │ serves /static (Next.js export) + │ injects __rep__ payload from environment vars + └ environment vars come from Kubernetes ConfigMap + Secret +``` + +No upstream server. No Node.js runtime in production. The final container is `FROM scratch`. + +## When to use this pattern + +- Next.js app with no server-side rendering requirements (pure CSR) +- Kubernetes deployments where you want to change feature flags without rebuilds +- Minimal container surface area (`FROM scratch`, ~8MB total image size) + +## File structure + + +- examples/nextjs-csr-embedded/ + - src/ + - app/ + - layout.tsx + - page.tsx + - components/ + - ConfigPanel.tsx Reads REP public + sensitive vars + - FeatureFlags.tsx Hot-reload-aware flag toggle display + - k8s/ + - configmap.yaml Standard envFrom pattern + - configmap-envfile.yaml Hot-reload variant (mounted as file) + - deployment.yaml Both variants documented + - secret.yaml Sensitive vars + - service.yaml + Ingress + - Dockerfile 3-stage: gateway + Next.js build + FROM scratch + - next.config.ts output: 'export' + - .rep.yaml + - .env.example + + +## Next.js static export + +Set `output: 'export'` in `next.config.ts`: + +```typescript +const nextConfig: NextConfig = { + output: 'export', + images: { unoptimized: true }, // required for static export +}; +``` + +The build produces an `out/` directory of static HTML/CSS/JS. This is what the gateway serves. + +## Reading REP vars in client components + +```tsx +'use client'; + +import { useRep, useRepSecure } from '@rep-protocol/react'; + +export function ConfigPanel() { + // Synchronous — available on first render + const apiUrl = useRep('API_URL', 'http://localhost:3001'); + const envName = useRep('ENV_NAME', 'development'); + + // Decrypted on demand (async) + const { value: analyticsKey, loading } = useRepSecure('ANALYTICS_KEY'); + + return (/* … */); +} +``` + +```tsx +'use client'; + +import { useRep } from '@rep-protocol/react'; + +export function FeatureFlags() { + // useRep() subscribes to hot reload — component re-renders + // when FEATURE_FLAGS changes without a page refresh + const rawFlags = useRep('FEATURE_FLAGS', ''); + const active = new Set(rawFlags.split(',').map(f => f.trim()).filter(Boolean)); + + return ( + + ); +} +``` + +## Dockerfile (FROM scratch) + +Three stages: download gateway, build Next.js, assemble scratch image: + +```dockerfile +FROM alpine:3.21 AS gateway +ARG GATEWAY_VERSION=0.1.3 +RUN apk add --no-cache curl ca-certificates && \ + curl -fsSL "…/rep-gateway_${GATEWAY_VERSION}_linux_amd64.tar.gz" \ + | tar -xz -C /tmp && \ + mv /tmp/rep-gateway /rep-gateway && chmod +x /rep-gateway + +FROM node:22-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci +COPY . . +RUN npm run build # produces out/ + +FROM scratch +COPY --from=gateway /rep-gateway /rep-gateway +COPY --from=builder /app/out /static +COPY --from=gateway /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +USER 65534:65534 +EXPOSE 8080 +ENTRYPOINT ["/rep-gateway", "--mode", "embedded", "--static-dir", "/static"] +``` + +## Kubernetes: ConfigMap for feature flags + +### Standard pattern (envFrom) + +Store all `REP_PUBLIC_*` vars in a ConfigMap. Sensitive vars go in a Secret: + +```yaml +# configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: rep-config +data: + REP_PUBLIC_API_URL: "https://api.example.com" + REP_PUBLIC_ENV_NAME: "production" + REP_PUBLIC_FEATURE_FLAGS: "dark-mode,new-checkout" # ← update this to toggle flags + REP_GATEWAY_MODE: "embedded" + REP_GATEWAY_HOT_RELOAD: "true" +``` + +```yaml +# secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: rep-secrets +stringData: + REP_SENSITIVE_ANALYTICS_KEY: "ak_live_replace_me" +``` + +Reference both in the Deployment: + +```yaml +envFrom: + - configMapRef: + name: rep-config + - secretRef: + name: rep-secrets +``` + +To update feature flags: + +```bash +kubectl edit configmap rep-config +# Change REP_PUBLIC_FEATURE_FLAGS, save + +kubectl rollout restart deployment/frontend +# New pods start with updated config +``` + +### Hot-reload variant (zero pod restarts) + +Mount the ConfigMap as a `.env` file and start the gateway with `--hot-reload --hot-reload-mode=file_watch`: + + +1. **Apply the env-file ConfigMap** + + ```bash + kubectl apply -f k8s/configmap-envfile.yaml + ``` + + The ConfigMap stores a `.env` file as a single key: + + ```yaml + data: + rep.env: | + REP_PUBLIC_API_URL=https://api.example.com + REP_PUBLIC_FEATURE_FLAGS=dark-mode,new-checkout + ``` + +2. **Mount the file in the Deployment** + + ```yaml + args: + - "--mode=embedded" + - "--static-dir=/static" + - "--env-file=/config/rep.env" + - "--hot-reload" + - "--hot-reload-mode=file_watch" + - "--watch-path=/config/rep.env" + volumeMounts: + - name: rep-config-volume + mountPath: /config + readOnly: true + volumes: + - name: rep-config-volume + configMap: + name: rep-config-envfile + ``` + +3. **Update feature flags** + + ```bash + kubectl edit configmap rep-config-envfile + # Change REP_PUBLIC_FEATURE_FLAGS in the rep.env block, save + ``` + +4. **Wait ~60 seconds** + + Kubernetes propagates ConfigMap changes to mounted files. The gateway detects the file change, re-reads vars, re-encrypts sensitive values with a fresh ephemeral key, and pushes a reload event to all connected browsers via `/rep/changes` (SSE). + +5. **Components re-render automatically** + + Any component using `useRep('FEATURE_FLAGS')` re-renders with the new value. No pod restart, no page refresh. + + + + +## Running locally + +### Docker + +```bash +cd examples/nextjs-csr-embedded + +docker build -t rep-nextjs-csr . + +docker run --rm -p 8080:8080 \ + -e REP_PUBLIC_API_URL=https://api.example.com \ + -e REP_PUBLIC_ENV_NAME=production \ + -e REP_PUBLIC_FEATURE_FLAGS=dark-mode,new-checkout \ + -e REP_SENSITIVE_ANALYTICS_KEY=ak_live_abc123 \ + rep-nextjs-csr +``` + +### CLI dev mode + +```bash +cd examples/nextjs-csr-embedded + +# Build the static export +npm run build # → out/ + +# Serve with gateway in embedded mode +npx @rep-protocol/cli dev \ + --mode embedded \ + --static-dir ./out \ + --env-file .env.local +``` + +## Comparison: static vs SSR + +| | CSR + Embedded (this example) | SSR + Proxy | +|---|---|---| +| Next.js `output` | `'export'` (static) | default (server) | +| Runtime container | `FROM scratch` (~8MB) | Node.js image (~150MB) | +| Server features | ❌ No API routes, no SSR | ✅ Full Next.js server | +| Feature flag updates | ConfigMap edit + SIGHUP (no restart) | ConfigMap edit + rolling restart | +| Gateway mode | `embedded` | `proxy` | + +Use the CSR + embedded pattern when you don't need server-side rendering or Next.js API routes. Use the [proxy mode example](/examples/nextjs-proxy/) when you do. diff --git a/docs/src/content/docs/examples/nextjs-proxy.mdx b/docs/src/content/docs/examples/nextjs-proxy.mdx new file mode 100644 index 0000000..bc96726 --- /dev/null +++ b/docs/src/content/docs/examples/nextjs-proxy.mdx @@ -0,0 +1,190 @@ +--- +title: Next.js — Proxy Mode +description: Run the REP gateway as a reverse proxy in front of a Next.js SSR server. Environment variables are injected into every HTML response without touching the Next.js build. +--- + +import { Aside, FileTree } from '@astrojs/starlight/components'; + +The REP gateway runs in **proxy mode** in front of a Next.js server. It intercepts every HTML response and injects the `__rep__` payload — no changes to the Next.js build pipeline required. + +Source code at [`examples/nextjs-proxy/`](https://github.com/ruachtech/rep/tree/main/examples/nextjs-proxy). + +## Architecture + +``` +Client → REP Gateway :8080 (proxy mode) + ↓ injects __rep__ payload into HTML responses + Next.js Server :3000 +``` + +The Next.js server is never exposed directly. All traffic flows through the gateway. Config lives entirely in the gateway's environment variables — the Next.js image is the same across every deployment. + +## When to use this pattern + +- Next.js App Router or Pages Router with server-side rendering (SSR) +- You want the full Next.js server (streaming, server actions, API routes) +- Docker Compose or Kubernetes multi-container pod deployments + +## File structure + + +- examples/nextjs-proxy/ + - src/ + - app/ + - layout.tsx + - page.tsx Server component shell + - components/ + - EnvDisplay.tsx Client component — reads REP vars + - Dockerfile Builds the Next.js server + - docker-compose.yml Gateway + Next.js as two services + - next.config.ts + - .rep.yaml + - .env.example + + +## Reading REP vars in Next.js + +REP runs entirely in the browser. In the App Router, use `'use client'` components: + +```tsx +'use client'; + +import { useRep, useRepSecure } from '@rep-protocol/react'; + +export function EnvDisplay() { + // Synchronous — available on first render, no loading state + const apiUrl = useRep('API_URL', 'http://localhost:3001'); + const envName = useRep('ENV_NAME', 'development'); + const features = useRep('FEATURE_FLAGS', ''); + + // Async — decrypted via the session-key endpoint + const { value: analyticsKey, loading } = useRepSecure('ANALYTICS_KEY'); + + return ( +
+

Environment: {envName}

+

API: {apiUrl}

+

Key: {loading ? 'loading…' : analyticsKey}

+
+ ); +} +``` + + + +## Docker Compose setup + +The [`docker-compose.yml`](https://github.com/ruachtech/rep/blob/main/examples/nextjs-proxy/docker-compose.yml) runs both services: + +```yaml +services: + gateway: + image: ghcr.io/ruachtech/rep/gateway:0.1.3 + ports: + - "8080:8080" + environment: + REP_GATEWAY_MODE: proxy + REP_GATEWAY_UPSTREAM: "app:3000" + # App config — change these per environment, NOT the Next.js image + REP_PUBLIC_API_URL: "https://api.example.com" + REP_PUBLIC_ENV_NAME: "production" + REP_PUBLIC_FEATURE_FLAGS: "dark-mode" + REP_SENSITIVE_ANALYTICS_KEY: "ak_live_abc123" + depends_on: + - app + + app: + build: . + expose: + - "3000" + environment: + NODE_ENV: production + # No REP_PUBLIC_* vars here — gateway injects them at request time +``` + +## Running locally + +### Docker Compose + +```bash +cd examples/nextjs-proxy +docker compose up +``` + +Open `http://localhost:8080`. The gateway proxies to Next.js and injects config. + +### Changing config without rebuilding + +```bash +# Edit docker-compose.yml to update REP_PUBLIC_* vars, then: +docker compose up -d gateway # restart only the gateway +``` + +The Next.js image is untouched. New config is live immediately. + +### CLI dev mode (no Docker) + +```bash +# Terminal 1 — run Next.js dev server +npm run dev # starts on :3000 + +# Terminal 2 — run REP gateway in proxy mode +npx @rep-protocol/cli dev --proxy http://localhost:3000 --env-file .env.local +``` + +Open `http://localhost:8080`. + +## Dockerfile (Next.js only) + +The Next.js Dockerfile builds a standalone server. The gateway is a separate image: + +```dockerfile +FROM node:22-alpine AS deps +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci --omit=dev + +FROM node:22-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + +FROM node:22-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +USER 1001 +EXPOSE 3000 +CMD ["node", "server.js"] +``` + +Enable standalone output in `next.config.ts`: + +```typescript +const nextConfig: NextConfig = { + output: 'standalone', +}; +``` + + + +## Kubernetes (proxy mode) + +For Kubernetes, run gateway and Next.js as a two-container pod or two separate Deployments with a Service in front of the gateway: + +```yaml +# Gateway reads config from a ConfigMap +envFrom: + - configMapRef: + name: rep-config + - secretRef: + name: rep-secrets +``` + +See the [Kubernetes deployment guide](/deployment/kubernetes/) for a full manifest. diff --git a/docs/src/content/docs/examples/simple-html.mdx b/docs/src/content/docs/examples/simple-html.mdx new file mode 100644 index 0000000..f5ac00d --- /dev/null +++ b/docs/src/content/docs/examples/simple-html.mdx @@ -0,0 +1,140 @@ +--- +title: Simple HTML — No Build Step +description: Use REP in a plain HTML file with no bundler or framework. The SDK is loaded via esm.sh and the gateway serves the file in embedded mode with a FROM scratch container. +--- + +import { Aside, FileTree } from '@astrojs/starlight/components'; + +A single HTML file using the REP SDK via [esm.sh](https://esm.sh), served by the gateway in embedded mode. No build step, no bundler, no framework. + +Source code at [`examples/simple-html/`](https://github.com/ruachtech/rep/tree/main/examples/simple-html). + +## When to use this pattern + +- Static marketing pages or single-file dashboards +- Quick prototyping without a build pipeline +- Demonstrating REP concepts with minimal infrastructure +- Environments where adding a Node.js build step is impractical + +## File structure + + +- examples/simple-html/ + - index.html Single HTML file — the entire app + - Dockerfile Two-stage: gateway download → FROM scratch + - .rep.yaml Optional manifest for validate/typegen + - .env.example Template environment variables + + +## How it works + +The REP gateway runs in **embedded mode**, serving `index.html` directly. When a browser requests the page, the gateway injects the `__rep__` payload before the response is sent: + +``` +Browser → REP Gateway :8080 (embedded mode) + ↓ injects + ↓ serves index.html +``` + +The HTML file imports the SDK as an ES module from esm.sh — no npm install, no bundler: + +```html + +``` + + -## Docker Compose setup +## Integrity verification -The [`docker-compose.yml`](https://github.com/ruachtech/rep/blob/main/examples/nextjs-proxy/docker-compose.yml) runs both services: +The example includes an `IntegrityBanner` client component that verifies the payload was not modified after the gateway injected it. The SDK checks a SHA-256 SRI hash embedded on the `