Vercel Cost Optimization: CDN images, static pages, Edge API, Cloudflare cache#158
Vercel Cost Optimization: CDN images, static pages, Edge API, Cloudflare cache#158xb1g wants to merge 30 commits into
Conversation
…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>
…ssions and path_content
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
web | a1d4c01 | May 11 2026, 06:32 PM |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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
/maptoforce-staticand 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). |
| "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, |
|
|
||
| result := ARRAY[]::TEXT[]; | ||
| FOR i IN 1..array_length(urls, 1) LOOP | ||
| result := array_append(result, _b2_to_cdn(urls[i])); | ||
| END LOOP; |
| -- path_assessment_submissions | ||
| UPDATE public.path_assessment_submissions | ||
| SET image_url = _b2_to_cdn(image_url) | ||
| WHERE image_url LIKE '%backblazeb2.com%'; | ||
|
|
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { createClient } from "@/utils/supabase/server"; | ||
| import { createHash } from "crypto"; | ||
|
|
| {/* Cloudflare Web Analytics */} | ||
| <script | ||
| defer | ||
| src="https://static.cloudflareinsights.com/beacon.min.js" | ||
| data-cf-beacon='{"token": "YOUR_CF_BEACON_TOKEN"}' |
| 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
|
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:
To read more about collaboration on Vercel, click here. |
Summary
Optimizes Vercel hosting costs across 5 areas:
1. Image CDN Migration
images.unoptimized: true— eliminates Vercel Image Optimization chargeslib/cdn-url.ts— B2 → CDN URL rewriting utilityhttps://cdn.passionseed.org/...across ~20 tables2. Static Page Conversion
/map→ force-static (was force-dynamic)/hackathon/sponsorship(5m),/epic-sprint(1h),/fi/exec(5m)3. API Edge Runtime
Buffer.from→btoa(), Node.jscrypto→Web Crypto4. Analytics Swap
5. Cloudflare Cache Rules
scripts/configure-cloudflare-cache-rules.sh— API (1h), static assets (1yr), CDN images (1yr)Before deploy
YOUR_CF_BEACON_TOKENinapp/layout.tsxwith real Cloudflare Web Analytics token