FeaturesDevelopersDocsChangelog
hello@givelink.ai
GGiveLink
For NonprofitsPricingCompareBlogAbout
Join the Waitlist
← All runbooks

Preview Environment Testing

How preview environments are created, what data they contain, how to authenticate, and how to test every major feature surface.

Last reviewed April 14, 2026

Preview Environment Testing

What it is

Every PR that targets main gets its own isolated preview environment — a Vercel preview deployment backed by its own Neon database branch. The database is a copy-on-write fork of production: it starts with all production tables and data (currently just the bootstrap state, since we're pre-launch), plus an additive test organization inserted by the preview seed.

Preview environments let you test schema migrations, new features, and UI changes without touching production or the demo environment.


Lifecycle

When a preview environment is created

Push to PR branch
    |
    v
Vercel detects preview deployment
    |
    v
Neon auto-creates a copy-on-write DB branch from production
(branch name: preview/<git-branch-name>)
    |
    v
Neon injects branch-specific DATABASE_URL into the deployment
(this overrides the generic Preview-scoped env var for this build)
    |
    v
vercel-build.sh runs:
  1. Log DB host (safety check — aborts if it resolves to production)
  2. CREATE EXTENSION IF NOT EXISTS vector (pgvector for org-intel embeddings)
  3. drizzle-kit migrate (applies any new migration files from the PR)
  4. seed.ts --preview (inserts Preview Test Org — additive, no truncate)
  5. pnpm build (builds Next.js)
    |
    v
Preview URL is live (e.g. givelink-abc123-datawake-vb.vercel.app)

Safety guard: If the DB host resolves to ep-restless-pond (production) — meaning preview branching isn't firing — the build script prints a loud !!! WARNING and skips migrations/seed rather than mutating production. The Next.js build still runs, but the app will use production data. See the troubleshooting entry "Preview connects to production database" for the fix.

When it's torn down

When a PR is closed or merged, the Neon integration deletes the preview database branch. The Vercel preview deployment stays accessible for a while but the database connection will fail. No manual cleanup needed.

How it differs from demo and production

ProductionDemoPreview
URLgivelink.ai / app.givelink.aidemo.givelink.aigivelink-{hash}-datawake-vb.vercel.app
DatabaseNeon main branch (ep-restless-pond)Neon demo branch (ep-silent-recipe)Neon auto-branch (copy-on-write from production)
AuthClerk (production instance)Bypassed (DEV_AUTH_BYPASS=true)Clerk (dev/preview instance)
DataReal (empty pre-launch)Hope Foundation canonical datasetCopy of production + Preview Test Org
Schema syncManual db:migrate before deployAuto via sync-demo-schema.yml (db:push)Auto via vercel-build.sh (drizzle-kit migrate)
PersistencePermanentPermanent (long-lived branch)Ephemeral (deleted on PR close)
Who can accessAnyone (public pages) / Clerk users (dashboard)Anyone (fully public, no auth)Vercel team members (SSO is off but URL is unguessable)

What data is available

Preview databases start as a copy-on-write snapshot of production at the moment the branch is created. Pre-launch, production is essentially empty (just the bootstrap schema). On top of that, the preview seed adds:

EntityDetails
Organization"Preview Test Org" (org_preview_test), slug preview-test, plan tier starter, status active
User"Preview Tester" (user_preview_test), email preview@test.givelink.ai, Clerk ID clerk_preview_test
Org membershipThe test user is owner of Preview Test Org
Campaign"Preview Test Campaign", donation type, active, $10,000 goal, suggested amounts $25/$50/$100

The seed is idempotent — if you re-trigger a deploy (push another commit), it won't duplicate the test org.

Post-launch behavior

Once production has real data, preview branches will inherit that data via copy-on-write. This is powerful for testing — you get realistic data without any setup. When PII becomes a concern, we'll create a preview-base Neon branch with sanitized data as the copy-on-write parent (documented in the spec, not yet implemented).


How to authenticate

Preview deployments use the Clerk dev/preview instance (the Preview-scoped CLERK_SECRET_KEY and NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY). This is a separate Clerk application from production.

Option A: Sign in via Clerk dev instance

  1. Open the preview URL
  2. Navigate to /sign-in (or click any dashboard link — you'll be redirected)
  3. Sign in with test credentials from the Clerk dev dashboard
  4. Select or create an organization in Clerk

This is the most realistic test path — it exercises the full Clerk flow including org selection, JIT provisioning, and role assignment.

Option B: Use SKIP_ENV_VALIDATION for build-only testing

The Preview scope has SKIP_ENV_VALIDATION=1 set. This means the app boots even if some env vars are misconfigured. It doesn't bypass auth at runtime — you still need to sign in. It just prevents build failures from env validation.

No DEV_AUTH_BYPASS on preview

DEV_AUTH_BYPASS is scoped only to demo/showcase, not generic Preview. Preview deployments always require Clerk sign-in. This is intentional — preview is for testing the real auth flow, demo is for unauthenticated sharing.


Testing scenarios

New visitor (no account)

What to test: Public pages, marketing site, donate flow (portal).

  1. Open the preview URL — you should land on the public marketing pages
  2. Navigate to /donate/preview-test-campaign (the seeded campaign's public donation page)
  3. Verify the campaign loads with correct details ($10,000 goal, suggested amounts)
  4. The donation form should render even without signing in

Edge case: Try navigating to /campaigns or other dashboard routes — you should be redirected to sign-in, not get a 500.

Logged-in user with seeded data

What to test: Dashboard, campaigns, contacts, settings.

  1. Sign in via Clerk dev instance
  2. If the preview seed ran correctly, you can access Preview Test Org's dashboard
  3. Verify the seeded campaign appears in the campaigns list
  4. Create a new campaign, contact, or event — it should persist within this preview branch only

Stripe Connect (onboarded vs not-onboarded)

What to test: Payment processing, Stripe onboarding flow, fee calculations.

  • Preview deployments use the Preview-scoped STRIPE_SECRET_KEY (Stripe test mode)
  • The seeded Preview Test Org starts not onboarded to Stripe Connect
  • To test the onboarding flow: navigate to Settings > Payments and start the Stripe Connect Express onboarding
  • Use Stripe test card numbers: 4242 4242 4242 4242 for successful payments
  • To test an already-onboarded org: complete the Stripe onboarding in the preview environment (it persists for the life of the PR)

Different access levels (roles)

What to test: Permission gating for owner, admin, editor, viewer.

GiveLink has four org roles: owner, admin, editor, viewer. The preview seed creates one user as owner. To test other roles:

  1. Create additional test users in the Clerk dev dashboard
  2. Sign in as each user and join the Preview Test Org
  3. Assign different roles via the org settings (as the owner)
  4. Verify permission boundaries: editors can't access billing, viewers can't edit campaigns, etc.

Plan tiers (feature gating)

What to test: Feature gating between starter, core, and pro tiers.

The preview seed creates the org on the starter tier. To test other tiers, update the org's planTier directly in the database:

-- Connect to the preview branch via Neon Console > Branches > [preview branch] > SQL Editor
UPDATE organizations SET plan_tier = 'pro' WHERE id = 'org_preview_test';

Verify that pro-only features (AI agents, advanced reporting, custom branding) appear/disappear based on tier.

Multi-tenant isolation

What to test: Org A can't see Org B's data (RLS enforcement).

  1. Create a second organization in the preview environment via the dashboard
  2. Add a campaign and contacts to each org
  3. Switch between orgs — verify campaigns/contacts from the other org are invisible
  4. Check API responses (browser devtools > Network) to confirm no cross-org data leakage

AI agents

What to test: Agent configuration, suggestion mode, auto mode.

  • Navigate to the AI Agents section in the dashboard
  • Agents use the Vercel AI Gateway — the Preview-scoped API keys should work
  • Test Off mode (no suggestions), Suggest mode (shows suggestions without acting), and Auto mode (acts autonomously)
  • Verify agent usage tracking records token counts and costs

Email flows

What to test: Donation receipts, email sequences, digest emails.

  • Preview deployments use the Preview-scoped RESEND_API_KEY (Resend test mode)
  • Resend in test mode logs emails but doesn't deliver to real inboxes
  • Check the Resend dashboard (resend.com) for sent emails after triggering:
    • A donation (receipt email)
    • An email sequence enrollment
    • A manual email from the contacts page

Salesforce integration

What to test: Connected vs disconnected Salesforce state.

  • The preview environment starts with no Salesforce connection (the preview seed doesn't configure one)
  • To test the connected flow, you'd need a Salesforce sandbox org connected via OAuth
  • For disconnected state: verify the Salesforce settings page shows the connection prompt, and that sync-dependent features degrade gracefully (no crashes, clear "not connected" messaging)

Edge cases

ScenarioHow to test
Empty org (no campaigns/contacts)Create a new org in the preview, don't add any data. Verify dashboard shows empty states, not errors.
Lapsed donorsIf post-launch with real data, look for contacts with old last-donation dates. Pre-launch, manually insert a contact with a lastDonationAt from 2 years ago.
Refunded paymentsProcess a test payment via Stripe, then refund it from the Stripe dashboard. Verify the refund syncs back.
Webhook replayUse the Stripe CLI (stripe trigger payment_intent.succeeded) to replay webhook events against the preview URL's /api/webhooks/stripe endpoint.

How to reset or re-seed

Light reset (re-run the additive seed)

The preview seed is idempotent — if the test org already exists, it skips. To force a re-seed:

  1. Delete the test org from the preview database (via Neon SQL Editor on the branch)
  2. Push a new commit to the PR branch (or re-run the Vercel deployment)
  3. The build will re-run seed.ts --preview and re-create the test org

Full reset (start with a fresh copy-on-write)

If the preview data is too messy to salvage:

  1. Delete the Neon preview branch manually (Neon Console > Branches)
  2. Push a new commit to the PR branch
  3. Neon creates a fresh copy-on-write branch from production
  4. The build applies migrations and seeds from scratch

Nuclear option (for stuck deploys)

If the Vercel deployment is stuck or the Neon branch is in a bad state:

# Force a fresh Vercel deployment
vercel deploy --yes

Environment variables on preview deployments

Preview deployments inherit env vars from two sources.

Injected by Neon per-deployment (branch-specific)

VariableValue
DATABASE_URLPooled connection to the preview Neon branch
DATABASE_URL_UNPOOLEDDirect connection to the preview Neon branch
PGHOSTPreview branch hostname
POSTGRES_*Various connection formats for the preview branch

These override the generic Preview-scoped values. Each deployment gets its own unique database.

Inherited from generic Preview scope (shared across all previews)

VariablePurpose
CLERK_SECRET_KEYClerk dev/preview instance
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYClerk client-side key
STRIPE_SECRET_KEYStripe test mode
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYStripe client-side test key
STRIPE_WEBHOOK_SECRETWebhook signature verification
RESEND_API_KEYResend test mode
INNGEST_EVENT_KEY / INNGEST_SIGNING_KEYInngest dev server
SKIP_ENV_VALIDATION1 — prevents build failures from missing optional vars
NEXT_PUBLIC_APP_DOMAINapp.givelink.ai
FIRECRAWL_API_KEYFor org intelligence features

Not available on preview (scoped to demo/showcase only)

VariableWhy
DEV_AUTH_BYPASSAuth bypass is demo-only — preview tests the real Clerk flow
DEV_BYPASS_ORG_IDOnly meaningful with auth bypass

Troubleshooting

"Build fails with migration error"

Cause: The PR branch has schema changes but is missing the corresponding migration file in drizzle/.

Fix: On the PR branch, run:

pnpm db:generate

This creates a numbered migration SQL file. Commit it and push — the preview build will pick it up.

"Build fails with relation already exists"

Cause: A migration file tries to CREATE a table that already exists. This usually means the branch is behind main and has stale migration files, or a migration was partially applied.

Fix: Rebase the PR branch onto main:

git fetch origin
git rebase origin/main
git push --force-with-lease

"Preview URL returns 500 on all routes"

Cause: Missing env vars. Check the Vercel build logs for env validation failed or CLERK_SECRET_KEY is required.

Fix: Verify the generic Preview-scoped secrets are set:

vercel env ls | grep Preview

The 7 required secrets are: CLERK_SECRET_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET, RESEND_API_KEY, CRON_SECRET.

"Preview connects to production database"

Symptom: Build logs show DB host: ep-restless-pond-... (the production endpoint) instead of a new preview branch endpoint. The safety guard in vercel-build.sh detects this and skips migrations/seed with a !!! WARNING message to prevent production mutation.

Cause: The Neon-Vercel integration's "Create database branch for deployment" option is not enabled on the project connection. The connection exists but doesn't include a preview deployment action, so Neon never creates a branch and the build inherits the production-pointing DATABASE_URL from the generic Preview scope.

Fix (must be done through Vercel UI — the API deploymentActions field does not persist the preview toggle):

  1. Go to Vercel Dashboard → givelink project → Storage → neon-pink-park
  2. Click the Projects tab
  3. Three-dot menu on the givelink row → Remove Project Connection → confirm
  4. Click Connect Project → select givelink
  5. Check Development, Preview, and Production under "Environments"
  6. Under "Create Database Branch For Deployment" check Preview (leave Production unchecked — you don't want a new branch for every production deploy)
  7. Click Connect

Gotcha: If the Connect form rejects with "already has an existing environment variable with name POSTGRES_URL…" or similar, the demo/showcase-scoped DB env vars are blocking the create (same name, same preview scope, just branch-qualified). Temporarily delete the 8 demo/showcase-scoped DB env vars, run Connect, then re-add them afterward with gitBranch: "demo/showcase".

Verification: After fixing, push a commit to any PR branch. You should see:

  • Neon Console → Branches — a new preview/<branch-name> branch
  • Vercel build log — DB host: ep-<something-other-than-restless-pond>
  • Connection config should include "deployments": {"actions": [{"environments": ["preview"], "slug": "Neon"}], "required": true}

"The seed says 'Preview test org already exists — skipping'"

Expected behavior. The seed is idempotent. If you need to recreate the test org, delete it from the database first (see How to reset).

"Preview Clerk keys silently point at production"

Symptoms:

  • Preview URLs (*.vercel.app or givelink-git-<branch>-datawake-vb.vercel.app) fail to render the sign-in page, redirect-loop, or throw Clerk domain not allowed errors in the browser console.
  • Served HTML contains pk_live_... instead of pk_test_....
  • CSP/script-src headers reference clerk.givelink.ai instead of *.clerk.accounts.dev.
  • CLERK_SECRET_KEY in the Preview scope is empty, malformed (surrounding " quote chars), or still a sk_live_... value.

Cause: The Preview scope was never pointed at the Clerk Development instance. Production Clerk is domain-locked to app.givelink.ai, so its pk_live_ key can't authenticate on any other hostname — every preview URL fails.

Detection:

# Pull the Preview scope into a temp file and inspect the Clerk values
vercel env pull .env.preview-check --environment=preview --yes
grep -E '^(NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY|CLERK_SECRET_KEY)=' .env.preview-check
# Bad: "pk_live_...", empty "", or "\"sk_live_...\""
# Good: "pk_test_...", "sk_test_..."
 
# Also check the served HTML on a preview URL
curl -sS https://givelink-git-<branch>-datawake-vb.vercel.app/sign-in \
  | grep -oE 'pk_(test|live)_[A-Za-z0-9]+' | head -1

Fix:

  1. Copy the pk_test_... and sk_test_... from the Clerk Development instance (dashboard.clerk.com → GiveLink app → switch instance dropdown → Development → API Keys). The dev instance uses a shared *.accounts.dev domain that works on any hostname — no domain lock.

  2. Rotate both scopes. Use the REST API, not the CLI — vercel env add <name> preview --value <v> --yes has a known bug where it returns action_required: git_branch_required even when --yes is passed and "all Preview branches" is the documented default (verified broken on CLI 51.1.0 and 51.2.1, 2026-04-14):

    PROJECT=$(jq -r .projectId .vercel/project.json)
    TEAM=$(jq -r .orgId .vercel/project.json)
     
    # Remove the bad values first (CLI rm works fine)
    vercel env rm NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY preview --yes
    vercel env rm CLERK_SECRET_KEY preview --yes
    vercel env rm NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY development --yes
    vercel env rm CLERK_SECRET_KEY development --yes
     
    # Add via REST API (works around the CLI bug)
    add_env() {
      curl -sS -X POST "https://api.vercel.com/v10/projects/${PROJECT}/env?teamId=${TEAM}&upsert=true" \
        -H "Authorization: Bearer ${VERCEL_TOKEN}" -H "Content-Type: application/json" \
        -d "$(jq -n --arg k "$1" --arg v "$2" --arg t "$3" --argjson tgt "$4" \
              '{key:$k, value:$v, type:$t, target:$tgt}')"
    }
    add_env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY 'pk_test_...' plain     '["preview"]'
    add_env CLERK_SECRET_KEY                  'sk_test_...' encrypted '["preview"]'
    add_env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY 'pk_test_...' plain     '["development"]'
    add_env CLERK_SECRET_KEY                  'sk_test_...' encrypted '["development"]'
  3. Trigger a redeploy (empty commit + push, or vercel deploy --yes from the branch).

  4. Verify the served HTML on the new preview URL contains pk_test_ and clerk.accounts.dev, zero pk_live_ or clerk.givelink.ai references.

"Clerk sign-in page shows 'Invalid publishable key'"

Cause: The Preview-scoped NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY doesn't match the Clerk application configured for development/preview.

Fix: Verify the key in Vercel matches the one in your Clerk dashboard (Development instance > API Keys). See the entry above ("Preview Clerk keys silently point at production") for the full rotation procedure.

"Stripe payments fail with 'No such connected account'"

Cause: The preview org hasn't completed Stripe Connect onboarding. Preview branches don't inherit Stripe Connect state from production — each preview starts fresh.

Fix: Complete the Stripe Connect Express onboarding flow in the preview environment's Settings > Payments page. Use Stripe test mode — no real bank account needed.


Cost

ResourceCost impact
Neon preview branchesCopy-on-write — near zero storage until data diverges. Compute charges only when active. Auto-suspend after 5 min idle.
Vercel preview deploymentsIncluded in Pro plan. Builds count toward monthly build minutes.
Stripe test modeFree
Clerk dev instanceFree tier
Resend test modeFree

Estimated added cost for preview environments: $0-5/month pre-launch, scaling with PR activity.


References

  • scripts/vercel-build.sh — the build script that runs migrations + seed for preview
  • scripts/seed.ts — production guard + --preview additive mode
  • drizzle.config.ts — uses DATABASE_URL_UNPOOLED for migrations
  • src/lib/dev-bypass.ts — why DEV_AUTH_BYPASS doesn't apply to preview
  • src/lib/auth.ts — org ID resolution with bypass fallback
  • src/proxy.ts — Clerk middleware and hostname routing
  • ADR-0047 — push to generate+migrate switch
  • Demo Environment runbook — the long-lived demo at demo.givelink.ai
  • Spec: docs/superpowers/specs/2026-04-13-preview-environment-spec.md
  • Plan: docs/superpowers/plans/2026-04-13-preview-environment-migration.md
GiveLink

Generosity, elevated.

Product

FeaturesFor NonprofitsPricingChangelogBlogDocsDevelopersRoadmap

Compare

GiveLink vs ZeffyGiveLink vs GivebutterGiveLink vs GoFundMe ProGiveLink vs Fundraise UpGiveLink vs DonorboxGiveLink vs Bloomerang

Company

About

Legal

PrivacyTermsAcceptable UseCookiesFee DisclosureTrademark

Our standing commitments

  • 1.No hidden fees. Any amount the donor is asked to cover is shown with a full breakdown — platform fee and processing fee itemized — before they submit.
  • 2.Every dollar you intended reaches your nonprofit. The fees you see on this page are the fees you pay — no tips, no surprise markup.
  • 3.We never call our fee a “tip” or a “contribution.” It's a fee. We name it. We show the number.

© 2026 GiveLink. All rights reserved.