The Framework Stack

By Narain · Supported · Astro, SvelteKit, Cloudflare Pages

Overview

A privacy-aligned framework stack has three core requirements:

  1. Static-first rendering: Minimize JavaScript, eliminate dynamic tracking.
  2. Privacy-respecting hosting: Edge compute without vendor lock-in, no user tracking.
  3. Schema-validated content: Ensure consistency with legal and technical requirements.

This guide covers the complete stack: Astro for content sites, SvelteKit for applications, Cloudflare Pages for hosting, security headers for enforcement, and Plausible for analytics.

Why Astro for Content Sites

Island Architecture: The Zero-JS Default

Astro uses island architecture: islands of interactivity in a sea of static HTML.

Traditional Single-Page Application (SPA):

All JavaScript included
JavaScript runs immediately on page load
Everything is interactive, even static content
Typical JS payload: 300-800KB

Astro Island Architecture:

Static HTML by default
JavaScript only for interactive components (islands)
Non-interactive content is pure HTML
Typical JS payload: 20-50KB

Example:

---
// This runs on the server (zero client JS)
import { posts } = await getCollection('posts');
---

<h1>Blog Posts</h1>

<!-- This is static HTML, no JavaScript -->
<ul>
  {posts.map(post => (
    <li><a href={post.url}>{post.data.title}</a></li>
  ))}
</ul>

<!-- This is an island: only this component has JavaScript -->
<CommentForm client:load />

The client:load directive means JavaScript is only loaded for the CommentForm component. The rest of the page is pure HTML.

Why this matters for privacy:

  • Minimal JavaScript = minimal opportunity for tracking.
  • Static HTML cannot fingerprint users or set cookies.
  • Reduces attack surface for injection attacks.
  • Faster page load = better user experience.

Content Collections with Zod Validation

Content Collections are Astro’s type-safe content management system.

Benefits:

  • Structured content with schema validation (via Zod).
  • Frontmatter is validated against a TypeScript schema.
  • Prevents invalid content from being published.
  • Enables type-safe rendering of content properties.

Example schema (for Privacy Stack guides):

import { defineCollection, z } from 'astro:content';

const guidesCollection = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    slug: z.string(),
    description: z.string(),
    number: z.string().regex(/^\d{2}$/),
    author: z.string(),
    author_url: z.string().url(),
    evidence_rating: z.enum(['Validated', 'Supported', 'Emerging', 'Forward-Looking']),
    keywords: z.array(z.string()),
    related_guides: z.array(z.object({
      slug: z.string(),
      title: z.string(),
      context: z.string()
    })),
    product_relevance: z.string()
  })
});

export const collections = {
  guides: guidesCollection
};

Usage in templates:

---
import { getCollection } from 'astro:content';
const guides = await getCollection('guides');
---

{guides.map(guide => (
  <article>
    <h1>{guide.data.title}</h1>
    <p>Rating: {guide.data.evidence_rating}</p>
    <p>{guide.data.description}</p>
  </article>
))}

Zod validation happens at build time. Invalid frontmatter prevents the build from completing.

Content Collections and Schema Validation

Content Collections provide a structured schema layer:

  • Guides are stored as Markdown with YAML frontmatter.
  • Build time Zod validation ensures schema compliance.
  • Same schema is used for HTML rendering and Markdown export.

Why SvelteKit for Applications

Astro is excellent for content sites but less suitable for interactive applications (dashboards, admin panels, real-time collaboration tools).

SvelteKit is a full-stack framework for application development:

  • Simple routing and data loading.
  • Server-side rendering (SSR) by default.
  • Form actions without JavaScript (progressive enhancement).
  • Efficient, minimal JavaScript by default.
  • Works well on Cloudflare Workers for edge SSR.

Comparison:

AspectAstroSvelteKit
Best forStatic content, blogs, marketing sitesInteractive apps, dashboards, forms
Default JSMinimal (only islands)Minimal (Svelte is efficient)
Build timeFast (static generation)Fast (SSR with caching)
Content managementContent Collections with ZodDatabases or headless CMS
HostingStatic pages + edge functionsEdge SSR (Cloudflare Workers)

Example SvelteKit dashboard (form action):

<form method="POST" action="?/submit">
  <input name="email" type="email" required />
  <button type="submit">Subscribe</button>
</form>

<script>
  import { enhance } from '$app/forms';
  export let form;
</script>

Server-side handler (+page.server.js):

export const actions = {
  submit: async ({ request, locals }) => {
    const data = await request.formData();
    const email = data.get('email');

    // Validate and process (no client-side JavaScript required)
    if (!email) return { error: 'Email required' };

    // Save to database
    await locals.db.saveEmail(email);

    return { success: true };
  }
};

This form works without client-side JavaScript. SvelteKit progressively enhances it with client-side handling if JavaScript loads.

Why Cloudflare Pages

Static + Edge SSR

Cloudflare Pages is a static hosting platform with edge computing capabilities:

  • Static files served from edge (Cloudflare’s 300+ global data centers).
  • Zero-cost edge SSR (runs on Cloudflare Workers, included free tier).
  • No vendor lock-in (exports work with other hosts).
  • Built-in WAF and DDoS protection.
  • Free tier: 500 builds/month, unlimited bandwidth.

Workers for Edge Compute

Cloudflare Workers are serverless functions that run at the edge (close to users):

  • Compute and respond in milliseconds (no round-trip to central servers).
  • Perfect for authentication, routing, rate limiting, and image optimization.
  • Priced per CPU-milliseconds used (pay for what you actually run).
  • Integrates with D1 (SQLite database at the edge) and KV (key-value store at the edge).

Example: Authentication at the edge:

// _middleware.ts (runs on every request)
export async function onRequest(context) {
  const { request } = context;
  const { pathname } = new URL(request.url);

  // Require auth for /admin paths
  if (pathname.startsWith('/admin')) {
    const cookie = request.headers.get('cookie');
    if (!cookie || !isValidSession(cookie)) {
      return new Response('Unauthorized', { status: 401 });
    }
  }

  return context.next();
}

This runs at the edge, protecting /admin routes without a central server.

Web Analytics Free Tier

Cloudflare Web Analytics provides free, cookieless analytics:

  • Integrated into Cloudflare Pages (no additional script required).
  • Counts unique visitors without cookies.
  • Shows Core Web Vitals, browser breakdown, geographic data.
  • Free tier: unlimited for up to 1M page views.

Security Headers: CSP, Permissions-Policy, Referrer-Policy

Content-Security-Policy (CSP)

CSP prevents injection attacks and enforces third-party elimination at the HTTP level.

Strict CSP for Privacy Stack:

Content-Security-Policy:
  default-src 'self';
  style-src 'self' 'unsafe-inline';
  script-src 'self' https://challenges.cloudflare.com;
  font-src 'self' data:;
  img-src 'self' data: https:;
  frame-ancestors 'none';
  report-uri /api/csp-report

What this does:

  • default-src 'self': Block all external resources by default.
  • style-src 'self' 'unsafe-inline': Allow inline CSS (necessary for Astro) and local stylesheets.
  • script-src 'self' ...: Allow local scripts and Turnstile only.
  • font-src 'self' data:: Allow only local fonts and data URIs.
  • img-src 'self' data: https:: Allow local images, data URIs, and HTTPS images.
  • frame-ancestors 'none': Prevent clickjacking (site cannot be framed).
  • report-uri /api/csp-report: Log violations for debugging.

Astro v5.9+ native CSP support:

// astro.config.mjs
export default defineConfig({
  security: {
    checkOrigin: true
  },
  build: {
    format: 'file' // Static HTML, not bundled
  }
});

Permissions-Policy

Permissions-Policy restricts which features scripts can access:

Permissions-Policy:
  accelerometer=(),
  camera=(),
  geolocation=(),
  gyroscope=(),
  magnetometer=(),
  microphone=(),
  payment=(),
  usb=()

This explicitly denies access to sensitive APIs even if a third-party script tries to use them.

Referrer-Policy

Referrer-Policy controls what information is sent to external sites via the Referer header:

Referrer-Policy: strict-origin-when-cross-origin

Levels of privacy:

  • no-referrer: Never send referrer (breaks analytics and some site features).
  • same-origin: Only send referrer to same domain.
  • strict-origin: Only send origin (not full URL) to cross-origin sites.
  • strict-origin-when-cross-origin: Send full referrer only same-origin; origin-only to cross-origin (recommended).

CORS Headers

CORS headers prevent unauthorized access to APIs:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400

Restrictive CORS prevents external sites from making requests to your APIs.

nosecone Library

nosecone (https://github.com/arcjet/nosecone) generates secure HTTP headers automatically.

Usage:

import { nosecone } from 'nosecone';

// Returns object with all recommended headers
const headers = nosecone({
  cspHeaderValue: "default-src 'self'; ...",
  permissionsPolicyHeaderValue: "geolocation=(), microphone=(), ...",
  referrerPolicyHeaderValue: "strict-origin-when-cross-origin",
});

// Apply to response
res.set(headers);

Alternative: Cloudflare’s managed WAF can apply headers automatically.

Static Site Generation: The Most Privacy-Aligned Rendering Strategy

Why Static is Privacy-First

Static HTML:

  • No server-side processing = no logging of user requests.
  • No database queries = no data collection.
  • No user tracking capability (analytics are optional, external, cookieless).
  • Cannot set cookies or create sessions (unless using optional JavaScript).

Server-side rendering (SSR):

  • Processes every request on the server.
  • Logs every request (IP, User-Agent, etc.).
  • Can set cookies, create sessions, and track users.
  • More opportunities for data collection.

For privacy-aligned websites: Static generation is the correct choice. The server does not see individual requests; the CDN serves cached HTML to all users.

Static Generation Workflow

1. Build time: Astro generates static HTML for all pages
2. Deploy: Static HTML uploaded to Cloudflare Pages
3. Runtime: User requests are served static HTML from edge
4. Analytics: Optional cookieless analytics track aggregated traffic
5. Result: No request logs, no user identification, no tracking

Complete Stack Recommendation

Frontend Layer

  • Framework: Astro for content, SvelteKit for applications
  • Styling: CSS Modules or Tailwind (no CSS-in-JS that requires JavaScript)
  • Fonts: Self-hosted WOFF2 (google-webfonts-helper, variable fonts)
  • Search: Pagefind (static site search, no external index)

Build & Deployment

  • Static generation: Astro static output
  • Edge SSR: Cloudflare Workers (for SvelteKit apps)
  • CDN: Cloudflare Pages (global edge caching)
  • Database: D1 (Cloudflare SQLite) for edge access or PostgreSQL for central database

Hosting & Compute

  • Pages: Cloudflare Pages (free tier, 500 builds/month)
  • Workers: Cloudflare Workers (free tier, 100,000 requests/day)
  • Authentication: WebAuthn/FIDO2 (self-hosted) or Clerk (third-party, but privacy-respecting)

Analytics

  • Platform: Plausible Analytics ($20-90/month) or Cloudflare Web Analytics (free)
  • Approach: Cookieless, no persistent identifiers
  • Alternative: Matomo self-hosted (complete control, open source)

Security

  • CSP Headers: Astro v5.9+ native support or nosecone library
  • WAF: Cloudflare Managed WAF (free tier available)
  • Bot Protection: Cloudflare Turnstile (free, invisible)
  • Certificate: Let’s Encrypt (auto-renewed, free)

Content

  • Content Collections: Astro Content Collections with Zod validation
  • Markdown: YAML frontmatter matching privacy-aligned schema
  • Content Format: Markdown with YAML frontmatter, Zod-validated schema

Implementation Checklist

Setup

  • Initialize Astro project: npm create astro@latest
  • Configure Content Collections in src/content/config.ts
  • Define Zod schema for all content types
  • Create /src/content/guides/ directory

Security Headers

  • Enable CSP in astro.config.mjs
  • Generate CSP headers with nosecone
  • Test CSP violations don’t block legitimate content
  • Set Permissions-Policy header
  • Set Referrer-Policy: strict-origin-when-cross-origin

Hosting

  • Create Cloudflare account (free tier)
  • Connect Pages to GitHub repository
  • Enable automatic deployments on push
  • Configure custom domain (if applicable)
  • Enable Cloudflare Web Analytics

Content Management

  • Create src/content/config.ts with Zod schema
  • Write frontmatter for all content pieces
  • Validate build completes without schema errors
  • Export content as Markdown

Analytics

  • Enable Cloudflare Web Analytics (free) or set up Plausible
  • Verify no cookies are set
  • Confirm analytics script is <2KB (compressed)
  • Test analytics in incognito/private mode

Third-Party Elimination

  • Audit all external resources (DevTools Network tab)
  • Self-host all fonts
  • Remove Google Fonts, Google Tag Manager, external CDNs
  • Replace third-party embeds with facades
  • Test no external data transmission in Network tab

Testing

  • Run build: npm run build
  • Test locally: npm run preview
  • Check Core Web Vitals in DevTools Lighthouse
  • Verify no console errors in DevTools
  • Test with browsers: Chrome, Firefox, Safari
  • Test mobile responsiveness

Performance Targets with This Stack

Typical results:

  • Lighthouse Performance: 90-100
  • First Contentful Paint (FCP): <1s
  • Largest Contentful Paint (LCP): <1.5s
  • Cumulative Layout Shift (CLS): <0.05
  • JavaScript payload: <20KB (compressed)
  • Total page weight: <50KB (compressed)

Comparison to traditional stack:

  • Traditional SPA: 300-800KB JS, 2-4s LCP
  • Privacy Stack: 20-50KB JS, <1.5s LCP
  • Improvement: 10-40x smaller JS, 2-3x faster

References


Related Guides