Guide 06
The Framework Stack
Overview
A privacy-aligned framework stack has three core requirements:
- Static-first rendering: Minimize JavaScript, eliminate dynamic tracking.
- Privacy-respecting hosting: Edge compute without vendor lock-in, no user tracking.
- 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:
| Aspect | Astro | SvelteKit |
|---|---|---|
| Best for | Static content, blogs, marketing sites | Interactive apps, dashboards, forms |
| Default JS | Minimal (only islands) | Minimal (Svelte is efficient) |
| Build time | Fast (static generation) | Fast (SSR with caching) |
| Content management | Content Collections with Zod | Databases or headless CMS |
| Hosting | Static pages + edge functions | Edge 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.tswith 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
- Astro Documentation: https://docs.astro.build
- SvelteKit Documentation: https://kit.svelte.dev
- Cloudflare Pages: https://pages.cloudflare.com
- Cloudflare Workers: https://workers.cloudflare.com
- Content Security Policy (MDN): https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- Permissions-Policy (MDN): https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy
- Referrer-Policy (MDN): https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
- nosecone: https://github.com/arcjet/nosecone
- Pagefind: https://pagefind.app
- OWASP: Content Security Policy (CSP)
Related Guides
- The Legal Foundation — Framework choices enforce compliance with all six conditions
- Third-Party Elimination — CSP headers enforce third-party elimination at the edge