Skip to main content
Cloudflare

Cloudflare Pages

How Arcturus-Prime.com is built, deployed, and served as a static+SSR hybrid on Cloudflare Pages

February 23, 2026

Cloudflare Pages

Arcturus-Prime is deployed as a hybrid static/SSR site on Cloudflare Pages. The static pages are served from Cloudflare’s global CDN edge nodes, and the dynamic routes (API endpoints, admin pages) run as Cloudflare Pages Functions — which are Workers under the hood.

Deployment Pipeline

The deployment pipeline is a three-hop chain: Gitea push triggers a GitHub mirror sync, which triggers the Cloudflare Pages build.

git push (Gitea)


git.Arcturus-Prime.com/KeyArgo/Arcturus-Prime   ← canonical repo

    │  mirror webhook

github.com/KeyArgo/Arcturus-Prime        ← read-only mirror

    │  CF Pages integration

Arcturus-Prime.pages.dev                  ← Cloudflare Pages build


Arcturus-Prime.com                        ← production (custom domain)

Gitea is the source of truth. The GitHub mirror exists solely because Cloudflare Pages requires a GitHub or GitLab repo for its CI/CD integration. A post-receive hook on Gitea pushes to GitHub, which triggers the Pages build automatically.

Build Configuration

SettingValue
Framework presetAstro
Build commandnpm run build
Build output directorydist/
Root directory/
Node.js version20.x
Package managernpm

The build command runs Astro’s build process, which outputs static HTML to dist/ for prerendered pages and bundles Functions for SSR routes into dist/_worker.js.

Framework Details

Arcturus-Prime runs on Astro 5.17 with the @astrojs/cloudflare adapter version 12.x. The adapter handles the translation between Astro’s SSR output and Cloudflare’s Workers runtime.

// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  output: 'server',
  adapter: cloudflare({
    platformProxy: {
      enabled: true,
    },
  }),
});

The output: 'server' setting makes SSR the default, with individual pages opting into static generation via export const prerender = true in their frontmatter script. In practice, about 81 pages are statically prerendered and 49 are server-rendered on each request.

Static vs. SSR

Astro’s hybrid rendering model lets each page choose its rendering strategy independently.

Static Pages (prerender = true)

These are generated at build time and served directly from the CDN:

  • All blog posts (/blog/[slug])
  • Journal entries (/journal/[...slug])
  • Documentation pages (/docs/[...slug])
  • Learning content (/learn/[...slug])
  • Public pages (/, /about, /contact, /homelab, etc.)

Static pages load instantly from the edge. No cold starts, no function invocations, no compute costs.

SSR Pages (prerender = false)

These run as Cloudflare Pages Functions on every request:

  • Admin pages (/admin/*) — need auth checks and live data
  • Dashboard (/dashboard) — personalized per user role
  • Auth routes (/auth/login, /auth/logout)
  • User portal (/user/*)
  • All API endpoints (/api/*)
  • Command center (/command/*)

SSR pages have access to the Cloudflare runtime: KV bindings, environment variables, request headers, and the full Workers API.

Pages Functions (Workers)

Every file in src/pages/api/ becomes a Cloudflare Pages Function. These are Workers scoped to the Pages project — they share the same deployment, bindings, and domain.

Functions have access to:

// Inside any SSR page or API route
const runtime = Astro.locals.runtime;
const env = runtime.env;

// KV namespace
const kv = env.ARGOBOX_CACHE;

// Environment secrets
const teamDomain = env.CF_ACCESS_TEAM_DOMAIN;
const accessAud = env.CF_ACCESS_AUD;

Compatibility Flags

The project requires specific Workers compatibility flags to work correctly:

# wrangler.toml (or CF Pages settings)
compatibility_flags = ["nodejs_compat"]

The nodejs_compat flag enables Node.js API compatibility in Workers (Buffer, crypto, etc.). The disable_nodejs_process_v2 flag was previously needed but may vary by adapter version.

Bundle Exclusions

Some packages are excluded from the SSR bundle because they are Node.js-only and would break in the Workers runtime:

  • better-sqlite3 — native SQLite binding, used only in dev mode
  • @argonaut/core — local package with Node.js dependencies

These are configured as external in the Astro/Vite config so they are never bundled into the Worker.

CSP and Security Headers

Content Security Policy and other security headers are defined in public/_headers, which Cloudflare Pages serves as HTTP response headers.

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()

/admin/*
  X-Robots-Tag: noindex, nofollow

/api/*
  X-Robots-Tag: noindex, nofollow

The CSP policy allows inline scripts (required by Astro’s hydration), connections to the Cloudflare Access endpoints, and fonts from the local domain. Admin and API routes get noindex to keep them out of search engines.

Preview Deployments

Every pull request gets its own preview deployment on a unique subdomain (e.g., abc123.Arcturus-Prime.pages.dev). Preview deployments are functionally identical to production — same build, same bindings, same Functions.

Admin Access in Previews

Preview deployments can access admin functionality by appending ?preview=true to any admin URL. This bypasses the normal Cloudflare Access authentication requirement so PR reviewers can test admin features without being in the Access policy.

https://abc123.Arcturus-Prime.pages.dev/admin?preview=true

This is only available on preview deployments (non-production URLs). The production domain at Arcturus-Prime.com always requires full Cloudflare Access authentication for admin routes.

Environment Variables

Environment variables are set in the Cloudflare Pages dashboard under Settings > Environment Variables. They are available to both the build process and the runtime Functions.

Build-Time Variables

VariablePurpose
NODE_VERSIONPin Node.js version for builds
NPM_FLAGSAdditional npm install flags

Runtime Secrets

VariablePurpose
CF_ACCESS_TEAM_DOMAINCloudflare Access team domain
CF_ACCESS_AUDAccess application audience tag
CF_ACCESS_ISSUERJWT issuer URL
OPENAI_API_KEYOpenRouter API key for AI features
GOOGLE_AI_KEYGoogle Generative AI key
RESEND_API_KEYTransactional email via Resend
CACHE_WARMUP_SECRETSecret for cache warmup cron

KV Bindings

Binding NameNamespacePurpose
ARGOBOX_CACHEARGOBOX_CACHEPrimary data store (roles, cache, sessions)

Custom Domain

The production site is served at Arcturus-Prime.com with Cloudflare handling DNS, SSL termination, and CDN caching. The Pages project is configured with:

  • Custom domain: Arcturus-Prime.com
  • SSL mode: Full (strict)
  • Always HTTPS: Enabled
  • Auto Minify: Disabled (Astro handles this)

The www.Arcturus-Prime.com subdomain redirects to the apex domain via a Cloudflare Page Rule.

Troubleshooting

Build Failures

The most common build failures are:

  1. Node.js version mismatch — Ensure NODE_VERSION matches what works locally (20.x).
  2. Missing environment variables — Functions that reference env.SOME_VAR will fail if the variable is not set in the Pages dashboard.
  3. Bundle size limits — Workers have a 1MB compressed limit. If the bundle exceeds this, check for accidentally included native modules.
  4. TypeScript errors — The build runs astro check which catches type errors that npm run dev might not surface.

Cold Starts

Pages Functions (Workers) have minimal cold starts compared to traditional serverless platforms. First requests after a deployment or after idle periods might see 5-50ms additional latency, but subsequent requests on the same edge location are near-instant.

cloudflarepagesdeploymentastroci-cd