Skip to content

superrare/superlinks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

122 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SuperLinks.me

All-in-one link-in-bio for creators. Built with React Router 7, Cloudflare Workers, Supabase, and Tailwind 4.

Stack

  • Framework: React Router 7 (SSR, declarative routes)
  • Runtime: Cloudflare Workers
  • Database: Supabase (Postgres + Auth + Realtime + Edge Functions)
  • Styling: Tailwind CSS 4 + shadcn/ui (new-york)
  • State: Zustand, React Hook Form, nuqs
  • Payments: USDC on Base via x402

Local Development

Prerequisites

Setup

# Install dependencies
pnpm install

# Copy local secrets template
cp .dev.vars.example .dev.vars
# Edit .dev.vars — Supabase URL, anon key, **service role key** (Worker purchase checks),
# plus Privy/Worker secrets referenced in this file.

# Optionally adjust `wrangler.jsonc` `vars` (placeholders checked in).
# Prefer Cloudflare Dashboard **secrets** for keys that must not ship in git (`wrangler secret put …`).

# Generate Cloudflare types
pnpm cf-typegen

# Start dev server
pnpm dev

Production deploy (GitHub Actions)

Merges to main run .github/workflows/deploy.yml. Treat GitHub Actions as the canonical production deploy path.

Important: .dev.vars exists only on your machine (gitignored). CI never reads it. The workflow runs scripts/deploy-worker-ci.sh, which passes wrangler deploy --var … so plaintext bindings (SUPABASE_URL, SUPABASE_ANON_KEY, PRIVY_APP_ID, PRIVY_SERVER_SIGNER_ID, etc.) come from GitHub repository Secrets, not from wrangler.jsonc placeholders. Without those secrets, the deploy step fails loudly instead of pushing your-* placeholders that break Privy/login.

After pnpm build, Wrangler reads .wrangler/deploy/config.json and deploys from build/server/wrangler.json, which still contains the your-* placeholders mirrored from wrangler.jsonc. A deploy that skips deploy-worker-ci.sh (--var overrides from GitHub Secrets or .dev.vars) can overwrite live plaintext Worker vars with those placeholders again.

Local deploys are optional. Use pnpm run deploy / pnpm run deploy:apply-env only when your shell or .dev.vars includes the deploy credentials (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID) plus the plaintext Worker vars listed below. pnpm run deploy:raw is the intentionally unsafe escape hatch for pnpm run build && wrangler deploy (no --var), useful only for manual debugging when plaintext bindings are managed elsewhere. Cloudflare Workers Builds is connected to the repo but is not the canonical deploy path unless its build environment is given the same required vars/secrets; when those vars are absent, deploy-worker-ci.sh intentionally skips deployment under WORKERS_CI=1 so Workers Builds can stay green without overwriting GitHub Actions deployments.

Repository secret Required
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID Yes
SUPABASE_URL, SUPABASE_ANON_KEY, PRIVY_APP_ID, PRIVY_SERVER_SIGNER_ID Yes
PUBLIC_SITE_HOST No — defaults to rare.xyz
BASE_NETWORK No — defaults to base-sepolia
WALLETCONNECT_PROJECT_ID No

Worker Secrets (e.g. PRIVY_APP_SECRET, APP_JWT_SECRET, SUPABASE_SERVICE_ROLE_KEY, R2_UPLOAD_SECRET) are not listed in committed vars; configure them once in the Cloudflare Dashboard (or wrangler secret put) — they persist across deploys unless you remove them.

Inspect Worker bindings from the CLI

Wrangler never prints secret values (wrangler secret list shows names only).

  • Plain-text vars as deployed: run scripts/cloudflare-worker-bindings.sh after export CLOUDFLARE_API_TOKEN=… and export CLOUDFLARE_ACCOUNT_ID=… in the same shell (these are not read from .dev.vars). Use real values, not placeholders: paste the 32-character lowercase hex from "account_id" in wrangler.jsonc—not the literal string your-account-id. Create a token under API Tokens with Workers Scripts → Read on the account. Optionally set CLOUDFLARE_WORKER_SCRIPT_NAME if it differs from superlinks. Output is Cloudflare API JSON — plaintext vars may expose values, so paste carefully.
  • Runtime hints (no secrets): open /login or /signup on the deployed Worker, then Cloudflare Observability → Workers Logs → search [superlinks-env]. Loaders emit masked diagnostics when placeholders are detected.

Supabase

Migrations live in supabase/migrations/. Push them with:

supabase db push

Generate TypeScript types:

SUPABASE_PROJECT_ID=your-project-id pnpm db:types

Scripts

Script Description
pnpm dev Start dev server
pnpm build Production build
pnpm run deploy Build + deploy-worker-ci.sh with --var (safe local path when .dev.vars includes deploy credentials)
pnpm run deploy:apply-env Build + deploy-worker-ci.sh with --var (loads .dev.vars locally; GH Actions invokes the script separately after pnpm run build)
pnpm run deploy:raw Build + raw wrangler deploy (escape hatch; can redeploy placeholder plaintext vars)
pnpm cf-typegen Generate Cloudflare + RR types
pnpm db:types Generate Supabase types
pnpm typecheck Type check
pnpm check Type check + build + deploy dry run
pnpm knip Find unused exports/deps
tests/test-app-hosting.sh Smoke checks for hosted ZIP apps (requires login + deployed worker for full run)
scripts/cloudflare-worker-bindings.sh Curl Worker …/workers/scripts/$name/settings (plaintext vars visible; secrets omitted)

Hosted ZIP apps (R2)

Creators upload a ZIP static site from Dashboard → Products (App) or edge add-app.

  • Served at /app/:productSlug/ on the Worker (paths with multiple segments are handled before React Router).
  • Legacy UUID deeplink /app/:uuid redirects to slug when known, otherwise falls back to the commerce edge/HTML embed path.

Infrastructure

  1. Cloudflare R2 bucket (rare-xyz by default — see wrangler.jsonc).
  2. Wrangler secrets: SUPABASE_SERVICE_ROLE_KEY (PostgREST purchase checks; never expose client-side); R2_UPLOAD_SECRET, APP_TOKEN_SECRET (must match commerce edge); APP_JWT_SECRET for sessions.
  3. Supabase commerce function secrets: same R2_UPLOAD_SECRET and APP_TOKEN_SECRET.
  4. WORKER_URL (recommended): HTTPS origin where this Worker is deployed (https://…workers.dev or a custom Workers hostname). Uploads PUT /api/r2-upload hit this URL. If unset, commerce uses https://PUBLIC_SITE_HOST instead — that hostname must terminate on this Worker. A marketing site or SPA host that does not route PUT /api/r2-upload to Workers will respond 404.

If this repo previously contained live Supabase keys in wrangler.jsonc, rotate anon and service-role keys in the Supabase dashboard after switching to placeholders.

Troubleshooting R2 upload failed … : 404

Edge add-app / update-app calls your Worker upload route. HTTP 404 means the URL ${WORKER_URL or site origin}/api/r2-upload is not handled by this worker (wrong host or domain not wired to Workers). Fix: set WORKER_URL on the commerce function to your Worker deployment origin, redeploy commerce, retry. Verify with something like curl -i -X PUT -H 'x-upload-secret: …' -H 'x-r2-key: test/ping' against that origin /api/r2-upload (expect 401 without secret match, 200 with correct secret—not 404 from the edge).

Login / signup stuck on “Loading…”

Committed wrangler.jsonc uses placeholder PRIVY_APP_ID, SUPABASE_*, etc. Every deploy that must carry real plaintext values needs deploy-worker-ci.sh / pnpm deploy:apply-env (CI secrets loaded as env vars, or .dev.vars locally). Editing plaintext vars only in the Cloudflare Dashboard is not enough if a later pnpm deploy (placeholder) runs. Without overrides, PrivyClientProvider never loads the SDK and the button stays idle. Also add https://YOUR-WORKER_SUBDOMAIN.workers.dev (and production domains) under Privy → Application → Domains / allowed origins so the iframe can initialize.

Project Structure

app/
├── routes/              # Thin route modules (loader/action/component)
├── features/            # Feature-first organization
│   ├── auth/            # Login, signup, callback, session helpers
│   ├── editor/          # Link page editor (My Links)
│   ├── store/           # Product CRUD, posts
│   ├── wallet/          # Earn, balance, transactions
│   ├── insights/        # Analytics
│   ├── messages/        # Chat with Supabase Realtime
│   ├── admin/           # Admin panel
│   ├── creator-page/    # Public /:handle page
│   ├── discover/        # Browse creators
│   └── app-viewer/      # Hosted app iframe
├── components/
│   ├── ui/              # shadcn/ui primitives
│   └── shared/          # Cross-feature components
├── lib/                 # Shared infra (env, supabase, commerce, cache)
├── stores/              # Zustand stores
└── types/               # Generated types

workers/
├── app.ts              # Cloudflare Workers entry (RR + R2 app gate)
└── app-hosting.ts      # R2 ingest + `/app/:slug/` static hosting

supabase/
└── migrations/          # SQL migrations (source of truth)

Routes

Path Description
/ Marketing landing page
/login Google OAuth login
/signup Claim username + sign up
/auth/privy/exchange Exchanges a Privy access token for an HttpOnly session cookie
/auth/refresh Refreshes the session cookie when the JWT is near expiry
/auth/logout Clears the session cookie
/docs CLI documentation
/discover Browse creators
/app/:id UUID deep link; redirects to /app/:slug/ when product has a slug (legacy embed otherwise)
/app/:slug/... Hosted static app files from R2 (Worker, not React Router)
/dashboard/links Editor: profile, links, theme
/dashboard/products Product + post management
/dashboard/insights Analytics
/dashboard/earn Wallet + transactions
/dashboard/messages Conversations
/dashboard/admin Admin panel
/dashboard/settings Account + theme toggle
/:handle Public creator page (catch-all, must be last)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors