Skip to content

Vercel Cost Optimization: CDN images, static pages, Edge API, Cloudflare cache#158

Open
xb1g wants to merge 30 commits into
mainfrom
thermal-mogote
Open

Vercel Cost Optimization: CDN images, static pages, Edge API, Cloudflare cache#158
xb1g wants to merge 30 commits into
mainfrom
thermal-mogote

Conversation

@xb1g

@xb1g xb1g commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Optimizes Vercel hosting costs across 5 areas:

1. Image CDN Migration

  • images.unoptimized: true — eliminates Vercel Image Optimization charges
  • lib/cdn-url.ts — B2 → CDN URL rewriting utility
  • DB migration rewrites all B2 URLs → https://cdn.passionseed.org/... across ~20 tables
  • CDN remotePattern added to next.config.mjs

2. Static Page Conversion

  • /map → force-static (was force-dynamic)
  • 3 pages → ISR: /hackathon/sponsorship (5m), /epic-sprint (1h), /fi/exec (5m)

3. API Edge Runtime

  • 19 high-traffic read-only API routes → Edge runtime
  • 4 crypto routes fixed: Buffer.frombtoa(), Node.js crypto→Web Crypto

4. Analytics Swap

  • Removed Vercel Analytics
  • Added Cloudflare Web Analytics (free)

5. Cloudflare Cache Rules

  • scripts/configure-cloudflare-cache-rules.sh — API (1h), static assets (1yr), CDN images (1yr)
  • Rules deployed on passionseed.org zone

Before deploy

  • Replace YOUR_CF_BEACON_TOKEN in app/layout.tsx with real Cloudflare Web Analytics token

xb1g and others added 19 commits May 5, 2026 04:53
…iter

- Rewrites s3.*.backblazeb2.com and f005.backblazeb2.com URLs to cdn.passionseed.org
- Pass-through for already-CDN and non-B2 URLs
- Handles null/undefined/empty input gracefully
- 17 Jest tests, 100% branch coverage

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…URLs

Migrates all Backblaze B2 image/file URLs across ~20 tables to Cloudflare CDN:
- S3-style B2: https://{bucket}.s3.{region}.backblazeb2.com/{path}
- Friendly B2: https://f005.backblazeb2.com/file/{bucket}/{path}https://cdn.passionseed.org/file/{bucket}/{path}

Covers:
- 22 TEXT columns (single URLs)
- 5 TEXT[] array columns (multiple URLs)
- 3 JSONB columns with embedded URLs (revisions, certificate_data)

Idempotent: filters rows with backblazeb2.com, pass-through for already-CDN URLs.
Wrapped in BEGIN/COMMIT with temporary helper functions cleaned up after.

Excludes:
- Supabase Storage URLs (profiles.avatar_url, hackathon_teams.team_avatar_url)
- _key columns (B2 file keys, not URLs)
- Non-image URL columns (website_url, linkedin_url, etc.)
- reflections.image_url (column does not exist in schema)

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…attern, optimize lucide-react imports

- Set images.unoptimized: true (not conditional on NODE_ENV)
- Add cdn.passionseed.org to remotePatterns
- Keep *.backblazeb2.com and *.supabase.co for backward compat
- Add optimizePackageImports for lucide-react

Feature: next-config-image-cdn

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Remove Vercel Analytics import and component to eliminate paid analytics usage.
Part of the cdn-image-config milestone.

- Remove: import { Analytics } from '@vercel/analytics/next'
- Remove: <Analytics /> JSX component

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Add async defer script tag loading beacon.min.js from cloudflareinsights.com
- Include data-cf-beacon attribute with site token placeholder (YOUR_CF_BEACON_TOKEN)
- Script placed at end of <body> for non-blocking load
- Vercel Analytics already removed in previous commit

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ic to force-static

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
… ISR

- app/hackathon/sponsorship/page.tsx: force-static + revalidate=300
- app/epic-sprint/page.tsx: force-static + revalidate=3600
- app/fi/exec/page.tsx: force-static + revalidate=300

Added try/catch wrappers for build-time resilience when DB is unavailable,
following the pattern from app/map/page.tsx.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…9 high-traffic read-only API routes

Converted routes:
- app/api/app-data
- app/api/user/next-nodes
- app/api/maps/list
- app/api/maps
- app/api/version
- app/api/hackathon/submissions
- app/api/hackathon/me
- app/api/hackathon/team/me
- app/api/hackathon/mentor/public
- app/api/hackathon/inbox
- app/api/hackathon/team/match/status
- app/api/hero-galaxy
- app/api/classrooms
- app/api/profile/dashboard
- app/api/tcas/projection
- app/api/tcas/categories
- app/api/onboarding/tcas
- app/api/check-node
- app/api/test-db

All routes are Supabase-only with no Node.js-specific APIs.
Auth-gated routes return 401 (not 500) for missing/malformed cookies.
Build succeeds with exit code 0.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…alents

- spotify/search: Buffer.from() → btoa()
- music/process: Buffer.from() → btoa()
- hackathon/track-view: createHash('sha256') → crypto.subtle.digest('SHA-256')
- expert-interview/session: import crypto → global crypto.randomUUID()

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…e rules via API

Creates scripts/configure-cloudflare-cache-rules.sh which configures 3 cache rules
for the passionseed.org zone via the Cloudflare Rulesets API:

1. API responses (/api/*): 1-hour TTL with stale-while-revalidate
2. Static assets (/_next/static/*): 1-year TTL (immutable hashed filenames)
3. CDN images (cdn.passionseed.org/*): 1-year TTL

Requires CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID env vars.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…DN migration

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…emicolons

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 6, 2026 01:35
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
web a1d4c01 May 11 2026, 06:32 PM

@vercel

vercel Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pseed Error Error May 6, 2026 5:35am

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying passionseed with  Cloudflare Pages  Cloudflare Pages

Latest commit: fe0b807
Status:🚫  Build failed.

View logs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Cost-optimization sweep for the Next.js + Supabase app to reduce Vercel spend by moving image delivery to a Cloudflare CDN, shifting some pages to static/ISR rendering, and migrating selected high-traffic API routes to the Edge runtime. It also replaces Vercel Analytics with Cloudflare Web Analytics and adds automation/docs for cache rules and rollout validation.

Changes:

  • Disable Next.js/Vercel image optimization and introduce a CDN URL rewriting utility + DB migration to move persisted B2 URLs to cdn.passionseed.org.
  • Convert /map to force-static and convert three public pages to ISR (revalidate).
  • Move multiple API routes to Edge runtime and adjust crypto/base64 usage for Edge compatibility; add a Cloudflare cache rules configuration script and Cloudflare analytics snippet.

Reviewed changes

Copilot reviewed 39 out of 39 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
supabase/migrations/20260505000000_migrate_b2_to_cdn.sql One-off migration to rewrite stored Backblaze URLs to CDN URLs across many tables/fields (including JSONB and arrays).
scripts/configure-cloudflare-cache-rules.sh Script to configure Cloudflare cache rules for /api/*, /_next/static/*, and the CDN hostname.
next.config.mjs Disables image optimization globally, allows cdn.passionseed.org, and enables optimizePackageImports for lucide-react.
lib/cdn-url.ts Adds toCdnUrl() helper for rewriting B2 URL patterns to CDN URLs.
lib/cdn-url.test.ts Adds unit tests covering URL rewrite patterns and pass-through cases.
app/map/page.tsx Switches /map to force-static.
app/layout.tsx Removes Vercel Analytics and injects Cloudflare Web Analytics beacon script.
app/hackathon/sponsorship/page.tsx Converts to ISR (5m) and adds error fallback for stats fetching.
app/fi/exec/page.tsx Converts to ISR (5m) and adds error fallback for data fetching.
app/epic-sprint/page.tsx Converts to ISR (1h) and adds error fallback for data fetching.
app/api/version/route.ts Marks route as Edge runtime.
app/api/user/next-nodes/route.ts Marks route as Edge runtime.
app/api/test-db/route.ts Marks route as Edge runtime.
app/api/tcas/projection/route.ts Marks route as Edge runtime.
app/api/tcas/categories/route.ts Marks route as Edge runtime.
app/api/spotify/search/route.ts Replaces Buffer.from(...).toString("base64") with btoa(...) for Edge/Web compatibility.
app/api/profile/dashboard/route.ts Marks route as Edge runtime.
app/api/onboarding/tcas/route.ts Marks route as Edge runtime.
app/api/music/process/route.ts Replaces Buffer.from(...).toString("base64") with btoa(...) for Edge/Web compatibility.
app/api/maps/route.ts Marks route as Edge runtime.
app/api/maps/list/route.ts Marks route as Edge runtime.
app/api/hero-galaxy/route.ts Marks route as Edge runtime.
app/api/hackathon/track-view/route.ts Replaces Node crypto hashing with Web Crypto (crypto.subtle.digest).
app/api/hackathon/team/me/route.ts Marks route as Edge runtime.
app/api/hackathon/team/match/status/route.ts Marks route as Edge runtime.
app/api/hackathon/submissions/route.ts Marks route as Edge runtime.
app/api/hackathon/mentor/public/route.ts Marks route as Edge runtime.
app/api/hackathon/me/route.ts Marks route as Edge runtime.
app/api/hackathon/inbox/route.ts Marks route as Edge runtime.
app/api/expert-interview/session/route.ts Removes Node crypto import; relies on global crypto.randomUUID().
app/api/classrooms/route.ts Marks route as Edge runtime.
app/api/check-node/route.ts Marks route as Edge runtime.
app/api/app-data/route.ts Marks route as Edge runtime.
.factory/analysis/validation-readiness.md Captures build/lint/test/typecheck readiness notes for the rollout.
.factory/analysis/static-pages-report.md Documents static/ISR conversion analysis for App Router pages.
.factory/analysis/image-delivery-report.md Documents image delivery patterns and CDN migration considerations.
.factory/analysis/dependency-readiness.md Documents dependency/tooling readiness and local Supabase availability status.
.factory/analysis/db-image-columns.md Documents DB columns that store image/file URLs and what should/shouldn’t be migrated.
.factory/analysis/api-edge-report.md Documents Edge migration analysis for API routes (edge-safe/possible/node-required).

Comment on lines +94 to +98
"expression": "(starts_with(http.request.uri.path, \"/api/\"))",
"description": "API responses: cache for 1 hour with stale-while-revalidate",
"action": "set_cache_settings",
"action_parameters": {
"cache": true,
Comment on lines +89 to +93

result := ARRAY[]::TEXT[];
FOR i IN 1..array_length(urls, 1) LOOP
result := array_append(result, _b2_to_cdn(urls[i]));
END LOOP;
Comment on lines +156 to +160
-- path_assessment_submissions
UPDATE public.path_assessment_submissions
SET image_url = _b2_to_cdn(image_url)
WHERE image_url LIKE '%backblazeb2.com%';

Comment on lines 1 to +3
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/utils/supabase/server";
import { createHash } from "crypto";

Comment thread app/layout.tsx
Comment on lines +171 to +175
{/* Cloudflare Web Analytics */}
<script
defer
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon='{"token": "YOUR_CF_BEACON_TOKEN"}'
Comment on lines +126 to +130
A Cloudflare CDN at `cdn.passionseed.org` exists (per task context) that proxies to `f005.backblazeb2.com`.

**Current B2 URL → CDN URL transformation:**
- `https://pseed-dev.s3.us-east-005.backblazeb2.com/images/maps/123_cover.webp`
- → `https://cdn.passionseed.org/file/pseed-dev/images/maps/123_cover.webp`
| Endpoint | `s3.us-east-005.backblazeb2.com` (prod) / `s3.us-west-000.backblazeb2.com` (default) |
| URL Pattern | `https://pseed-dev.s3.{region}.backblazeb2.com/{path}` |
| Friendly URL | `https://f005.backblazeb2.com/file/pseed-dev/{path}` |
| CDN (exists, unused) | `https://cdn.passionseed.org/file/pseed-dev/{path}` |
Table: node_content (also referenced as map_content)
- content_url (likely stores B2 URLs because: stores video/canva links; can be B2-hosted content or external links like YouTube)

Table: learning_maps
- Create lib/hackathon/db-edge.ts for Edge-compatible database operations
- Update Edge routes to import from db-edge instead of db
- Remove top-level env var throws in admin.ts that fail at build time
- Improve error messages in server.ts and admin.ts
- Move createClient() calls from module-level to inside each function
- Fix client.ts to validate env vars at call time, not import time
- Fix ceo-dashboard.ts, education.ts, community.ts
- Fix createClient() throwing during SSR/prerender when env vars unavailable
  (Cloudflare Pages build). Returns Proxy that defers error until actual use.
- Add singleton pattern to reuse browser Supabase client.
- Refactor hackathon edge routes to import from auth-edge instead of auth
  (separates Node.js crypto imports from edge runtime code).
- Remove stale ralph-loop.local.md state file.
- Update Supabase CLI temp version.
Configure assets directory for wrangler deploy command.
- Add build:workers script using next-on-pages to create Worker-compatible bundle
- Update wrangler.jsonc to point to .vercel/output/static output directory
- Fix worker name to match CI expected name (web)
- Remove @cloudflare/next-on-pages (designed for Pages, not Workers)
- Change build to 'next build && next export' for static site generation
- Add output: export and distDir: out to next.config.mjs
- Create wrangler.toml with [site] bucket pointing to out/ directory
- This serves static HTML via Cloudflare Workers Sites
- Install @opennextjs/cloudflare and run migration
- Upgrade Next.js from 16.2.1 to 16.2.3 (required by adapter)
- Remove all export const runtime = 'edge' declarations (not supported)
- Add wrangler.jsonc with nodejs_compat and assets binding
- Add open-next.config.ts for Cloudflare-specific config
- Add preview/deploy/upload scripts to package.json
- Configure ASSETS binding and WORKER_SELF_REFERENCE service
@vercel

vercel Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Unable to deploy a commit from a private repository on your GitHub organization to the wachaa1319's projects team on Vercel, which is currently on the Hobby plan. In order to deploy, you can:

  • Make your repository public or
  • Upgrade to Pro. A Pro subscription is required to deploy from a private organization repository.

To read more about collaboration on Vercel, click here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants