Building a Modern Membership Platform with Astro, Paddle, and Cloudflare

How to build an enterprise-grade content and course platform with Astro 5, Paddle subscriptions, Supabase, and Cloudflare edge computing. Architectural decisions, integrations, and Claude Code workflow.

Ceyhun Enki Aksan
Ceyhun Enki Aksan Entrepreneur, Maker

An enterprise-grade membership platform powered by modern technologies, edge computing, and AI-assisted development.

This post explains how to build a productizable membership platform with Astro 5, Paddle, and Cloudflare Pages. I’m presenting an architecture that combines Astro’s static site generation power, Paddle’s subscription management, and Cloudflare’s edge computing infrastructure to optimize performance, security, and developer experience.

My goal is to transform this system—which I develop solo and manage alongside Claude Code during development, and Qwen via LM Studio locally for migrating content from Grav CMS to Astro—into a reusable template for developers with similar needs. Beyond traditional blogs or CMS platforms, it features enterprise capabilities like tier-based access control, premium content gating, and automated webhook processing.

Why This Architecture?

When building a modern content platform, you face three fundamental challenges:

  1. Performance vs Dynamic Content: Static sites are fast, but membership systems require dynamic functionality
  2. Payment Integration: Subscription management, webhooks, invoicing, and tax complexity
  3. Global Distribution: Low latency, CDN, and edge computing requirements

This architecture solves these problems with a hybrid approach:

  • Static pages are generated at build-time with Astro (speed)
  • Dynamic APIs run at the edge via Cloudflare Pages Functions (flexibility)
  • Subscription logic is delegated to Paddle (complexity externalized)
  • Database is managed with Supabase (real-time, secure)

Technology Stack

Frontend & Build

TechnologyVersionPurpose
Astro5.15.2Static Site Generator + Edge Functions
TypeScript5.9.3Type-safe development
TailwindCSS3.4.17Utility-first CSS
MDX4.3.9Markdown + JSX components
Pagefind1.4.0Client-side search indexing

Backend & Services

TechnologyPurpose
SupabasePostgreSQL database, authentication, real-time
PaddleSubscription management, payments, invoicing
Cloudflare PagesHosting, edge functions, global CDN
Cloudflare KVKey-value cache (search, premium content)
Cloudflare R2Object storage (images, assets)

Project Structure

ceaksan-v4.0/
├── src/
│   ├── components/         # 68+ Astro components
│   │   ├── pages/         # Page-specific components
│   │   ├── ui/            # UI component library
│   │   └── sections/      # Reusable sections
│   ├── content/           # Astro Content Collections
│   │   ├── posts/         # Blog posts (MDX)
│   │   └── courses/       # Video courses
│   ├── lib/               # 52 library modules
│   │   ├── paddle/        # Paddle integration
│   │   ├── supabase.ts    # Server-side client
│   │   └── supabase-browser.js  # Browser client
│   ├── pages/             # File-based routing
│   └── styles/            # Global styles
├── functions/             # Cloudflare Pages Functions
│   └── api/               # 15 API endpoints
├── .claude/               # Claude Code integration
│   ├── commands/          # Slash commands
│   ├── subagents/         # Specialized agents
│   └── hooks/             # Automatic hooks
└── public/                # Static assets

Astro Configuration

When working with Astro 5, there’s a critical rule: Never use output: 'hybrid'—this mode doesn’t exist in Astro 5.

The Cloudflare Free Tier Challenge

A key decision in this project was not using Cloudflare’s official Astro adapter. Why?

Cloudflare Pages’ free tier has specific build limits. When using the @astrojs/cloudflare adapter, the dependencies and build process required by the adapter pushed against these limits. For more details, see Cloudflare’s Astro deployment guide.

Solution without the adapter:

// astro.config.mjs
export default defineConfig({
  output: 'static',  // CRITICAL: Always 'static'

  i18n: {
    defaultLocale: 'tr',
    locales: ['tr', 'en'],
    routing: {
      prefixDefaultLocale: true  // /tr/page and /en/page
    }
  }
  // NOTE: Cloudflare adapter NOT used
});

Pros and Cons of This Approach

Pros:

  • Staying within Cloudflare free tier limits
  • Smaller build output
  • Faster build times
  • Fewer dependencies

Cons:

  • Cloudflare-specific features (Image CDN, etc.) not directly usable
  • Manual functions/ directory management for server-side rendering
  • Missing automatic optimizations the adapter would provide

Dynamic Pages

For dynamic pages, Cloudflare Pages Functions are used in the functions/ directory:

// functions/api/example.ts
export const onRequest: PagesFunction = async (context) => {
  // KV, R2, D1 access available here
  return new Response(JSON.stringify({ data }));
};

For Astro pages, prerender can be disabled on a per-page basis:

// Dynamic page example
export const prerender = false;

Benefits of this approach:

  • Static pages are generated at build-time (fast, cheap)
  • Dynamic pages run at the edge (KV, R2 access)
  • Bundle size is optimized (no unnecessary SSR code)
  • Free tier limits are respected

Paddle Subscription Integration

Tier System

The platform offers a three-tier membership system:

TierAccess
InsiderContent
MakerContent + Files + Code
MasterEverything + Courses

Critical Rule: Course access is only available with Master tier. This is validated via price_id—not tier name. Because tier names may change over time, but Paddle’s price_id remains consistent.

Webhook Processing

Paddle webhooks are processed at the /api/paddle/webhook endpoint:

// Webhook event types
- subscription.created    // New subscription
- subscription.updated    // Plan change
- subscription.canceled   // Cancellation
- transaction.completed   // Payment confirmation

Each webhook event:

  1. Signature verification is performed
  2. Logged to webhook_events table
  3. subscriptions table is updated
  4. event_id is checked for idempotency

Subscription Update Logic

// Upgrade: Immediate, prorated
proration_billing_mode: 'prorated_immediately'

// Downgrade: Next billing period
proration_billing_mode: 'prorated_next_billing_period'

// Cancellation: Access continues until period end
effective_from: 'next_billing_period'

Supabase Database Architecture

Supabase forms the core database infrastructure of the platform:

  • Customer management: User information and Paddle integration
  • Subscription tracking: Subscription status, plan info, billing
  • Course enrollments: Enrollment management and access control

Every table is secured with Row Level Security (RLS). Users can only access their own data.

Access Control Flow

1. User wants to access premium content
2. Auth check → Supabase session
3. Tier validation → price_id based check
4. Result: Show content or paywall

Cloudflare Integration

KV Namespaces (6 total)

NamespaceUsage
PREMIUM_CONTENTPremium post content
COURSE_CONTENTCourse content
ANALYTICSUsage metrics
FEATURE_FLAGSFeature flags
RATE_LIMITERRate limiting
SESSIONSession management

R2 Object Storage

Bucket: your-assets
URL: https://assets.your-domain.com
Usage: Images, files, media

API Endpoints

The platform includes APIs running on Cloudflare Pages Functions:

  • Paddle integration: Webhook processing, subscription management
  • Content access: Premium content and course delivery
  • Pricing: Dynamic price information

Search with Pagefind

Pagefind was chosen over Algolia or ElasticSearch:

  • Client-side search: No server costs
  • Build-time indexing: Automatic
  • Multilingual support: Separate TR and EN indexes
  • Small bundle: ~100KB
// Automatic indexing after build
// Created in dist/pagefind/ directory

// Search usage
const pagefind = await import('/pagefind/pagefind.js');
await pagefind.init();
const results = await pagefind.search('astro');

With the latest update, Pagefind search is connected to the header search button. When users click the search button, the Pagefind modal opens and language-based filtering is automatically applied.

Premium Content Gating

Tier Definition via Frontmatter

---
title: "Premium Content"
requiredTier: ['insider']  # Array format
member: true
---

Client-Side Access Check

import { checkContentAccess } from '@/lib/content-access-client.js';

const hasAccess = await checkContentAccess(getSupabaseClient, {
  requiredTier: ['insider'],
  onAccessGranted: () => showContent(),
  onAccessDenied: () => showPaywall()
});

Premium Content Delivery

Premium content is not rendered client-side. It’s fetched via API:

// /api/content/premium-post
// 1. Auth check
// 2. Tier validation
// 3. Fetch content from KV
// 4. Return rendered HTML

i18n (Multilingual Support)

URL-Based Routing

/tr/contents/   → Turkish content
/en/contents/   → English content

Language Detection

const lang = window.location.pathname.startsWith('/en') ? 'en' : 'tr';
const text = lang === 'tr' ? 'Turkish Text' : 'English Text';

Content Collections

src/content/posts/2026/01/01.slug/
├── tr.mdx    # Turkish version
└── en.mdx    # English version

Claude Code Integration

Verification-First Approach

Give Claude a way to verify its work — Boris Mann

This principle forms the foundation of developing with Claude Code. Every change must be verifiable.

Slash Commands

Slash commands defined for the project:

/test-and-build     - Run tests, build
/verify-changes     - Comprehensive verification
/commit-push-pr     - Commit + Push + Create PR
/preview-deploy     - Deploy to preview environment
/kv                 - KV management

Custom Skills

Skills defined in .claude/commands/:

  • test-and-build.md: Build and test automation
  • verify-changes.md: Change verification
  • preview-deploy.md: Preview deployment
  • kv.md: KV namespace management

Each skill ensures Claude Code performs specific tasks consistently and verifiably.

Hooks

Automatically running hooks:

.claude/hooks/
├── PostToolUse.sh   # Formatting after each code change
└── AgentStop.sh     # Verification at each agent completion

CLAUDE.md Rules

Project-specific rules are defined in the CLAUDE.md file. This file helps Claude Code understand project rules:

## CRITICAL RULES

### NO HARDCODED VALUES
// WRONG:
const price = '$49.99'
const planName = 'Tier 2 - Professional'

// CORRECT:
Dynamic fetch from API
Real values from database

This approach prevents hardcoded values from leaking into code and ensures a consistent development experience.

Build and Deployment

npm Scripts

# Development
npm run dev                 # Dev server

# Build
npm run build              # Production build
npm run build:kv-data      # Search KV data
npm run build:premium-kv   # Premium content KV

# Deployment
npm run deploy:pages       # Cloudflare Pages
npm run preview           # Local preview

Build Process

1. Astro build → dist/
2. Pagefind indexing → dist/pagefind/
3. KV data generation (optional)
4. Cloudflare Pages deployment

Performance Optimizations

Bundle Optimization

// Vite config
- Manual chunk splitting (supabase, paddle separate)
- Asset inline limit: 0 (never inline)
- CSS immutable (1 year cache)

Image Optimization

  • Build-time optimization with Sharp
  • R2 CDN delivery
  • Lazy loading
  • WebP/AVIF formats

KV Caching

- Search content: Within PREMIUM_CONTENT
- Premium posts: PREMIUM_CONTENT
- Course content: COURSE_CONTENT

Security

Headers

Content-Security-Policy: [detailed CSP]
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

Rate Limiting

// RATE_LIMITER KV namespace
// Per-endpoint throttling

Webhook Signature Verification

// Paddle webhooks are verified with signature
const isValid = verifyPaddleSignature(request, secret);

Metrics

MetricValue
Astro Version5.15.2
Components68+
API Endpoints15
KV Namespaces6
Language Support2 (TR, EN)
Subscription Tiers3
Build Output~200MB

Conclusion

This architecture presents a productizable membership platform template where modern web technologies come together:

  • Performance: Astro SSG + Cloudflare edge
  • Flexibility: Hybrid rendering, dynamic APIs
  • Scalability: KV caching, R2 storage
  • Security: CSP, rate limiting, webhook verification
  • Developer Experience: Claude Code, slash commands, hooks
  • Cost Optimization: Staying within Cloudflare free tier limits

It’s possible to create a cost-efficient solution by staying within Cloudflare’s free tier. However, this requires giving up some advantages of the official adapter. For this reason, when I productize the project, I’ll offer it in two different packages. If you want to stay updated on developments and updates, you can follow me on X.

In summary, What did I build? Not a blog, but a reusable platform template. Who is it for? Developers looking to build a similar membership system. Why is it different? Because performance, security, and cost balance were considered together.