2026-01-18 | PreviewProof Team
Making Your Repo Preview-Ready: A Step-by-Step Audit
Most codebases are 80% preview-ready and don’t know it. The 20% that isn’t is concentrated in a handful of specific places — the OAuth callback that hardcodes a hostname, the seed script that doesn’t exist, the webhook URL stored in a database row from production, the env var read three different ways. Find those, fix them, and the team can adopt previews next week.
This is a Friday-afternoon audit. Pick a real codebase. Walk through each section in order. For each finding, decide: fix now, file a ticket, or note as “fine for now.” Cross-link to the relevant deep-dive when you want detail.
Step 1: How does the database get into a useful state?
Look for a seed file. db/seeds.rb, prisma/seed.ts, seed.sql, seeds.py.
If it doesn’t exist: number one fix. An empty database is unreviewable.
If it’s stale: check whether running it produces a database the application can be tested in. Can you log in? Are dashboards populated? If you’d have to manually create data after seeding, it’s stale.
If it works: check whether it uses faker or hardcoded data. Hardcoded is fine; copying production data is not. Search for pg_dump, mongodump, or production in seed-adjacent files. P0 if found.
Deep dives: Seeding a Postgres Database, Synthetic Data and Test Fixtures, Handling PII in Preview Seed Data.
Step 2: Do migrations run on boot?
Find the entrypoint — Dockerfile CMD, Procfile release phase, Kubernetes init container. Check that migrations run automatically before the app starts. prisma migrate deploy, alembic upgrade head, rails db:migrate, php artisan migrate --force — non-interactive, idempotent.
Common findings: migrations run only in CI; require manual approval; use the wrong command (prisma migrate dev instead of prisma migrate deploy); exist but don’t block startup if they fail.
Fix: move into the entrypoint, make it block app startup, use the deploy/apply command not the generate command. Deep dive: Database Migration Patterns.
Step 3: How are environment variables loaded?
Search for process.env, os.getenv, ENV[, getenv(. Findings to flag: hardcoded URLs anywhere; configuration loaded from a file with no checked-in .example version; a BASE_URL or APP_URL referenced inconsistently; secrets read directly from process.env in twenty places (config-drift smell).
Fix: centralize config loading into one module. Every URL, secret, and external endpoint read from env. Provide an .env.example listing every variable. Deep dive: Environment Variables in Ephemeral Preview Environments.
Step 4: OAuth callbacks, redirects, absolute URLs
Search for omniauth, passport, next-auth, authlib, social-auth. Is the OAuth callback URL hardcoded, configured per-environment with a static value, or derived from the request? Hardcoded is broken. Static-per-environment is broken in previews. Request-derived is right — but make sure it respects X-Forwarded-Host and X-Forwarded-Proto behind a load balancer.
Same audit for SAML, Stripe Connect, magic-link emails, password resets. Anywhere the application generates a URL that gets sent to a third party and bounced back, the URL must reflect the preview hostname. Deep dive: OAuth Callback URLs in Ephemeral Previews.
Step 5: Webhooks (incoming and outgoing)
Outgoing: search for code that sends webhooks to user-configured URLs. Do they fire in preview against the user’s real production webhook handler? P0 if so — preview must never make outbound calls to user infrastructure.
Incoming: Stripe, GitHub, Twilio push events to URLs your app exposes. In preview those URLs are ephemeral. Use a webhook capture proxy (smee.io, ngrok, Hookdeck) and route from there.
Fix: gate outgoing webhooks on PREVIEW_ENV, suppress or redirect to a capture endpoint. For incoming, set up a stable preview-webhook URL that fans out by PR number. Deep dive: Webhook Handling for Preview URL Changes.
Step 6: Service-to-service authentication
If you have multiple services, each preview needs them to talk to each other. Common findings: services hardcode each other’s URLs; services share a JWT secret from a static config file; services use mTLS with certificates that only exist in production.
Fix: parameterize service URLs from env. Use the same secret-loading mechanism with preview-specific test secrets. For mTLS, generate per-preview certs at deploy time. Deep dive: Service-to-Service Auth for Multi-Service Previews.
Step 7: Feature flags
Find the library: LaunchDarkly, Unleash, Flipper, Statsig, Split, ConfigCat. Do flags default sensibly in preview? In production they usually default off so unfinished features don’t leak. In preview they should default on so the feature under review is visible without manual toggling. Most teams have this backward.
Findings: flag state in preview pulls from production’s flag service; no way for a PR to specify “enable these flags.” Deep dive: Feature Flags in Preview Environments.
Step 8: Background jobs
Find the job runner — Sidekiq, Celery, BullMQ, Hangfire, Oban. Does the worker run in preview? Are scheduled jobs suppressed? Is the queue isolated per preview?
Common findings: cron schedules fire in preview, sometimes hitting third-party APIs; workers share Redis with other previews; workers are disabled entirely, making async features unreviewable. Fix: per-preview Redis, suppressed cron, time-bounded execution. Deep dive: Background Jobs in Ephemeral Preview Environments.
Step 9: Third-party integrations
Find Stripe, Twilio, SendGrid, Plaid, AWS SES, Mailgun. Is preview using test mode credentials? If sending real emails or charging real cards, P0. Less obvious: integrations without a clean test mode — a geocoding API charging per request, an LLM provider with no sandbox. Use a preview-aware mock or accept the cost. Deep dive: Stripe, Twilio, and SendGrid in Test Mode.
Step 10: Repo strategy and build graph
Monorepo: is there a build tool that knows what services changed? Nx, Turborepo, Bazel, Pants. Without one, every preview rebuilds everything.
Polyrepo: is there a way for a PR to specify “deploy with this branch of the other repo”? A deployment manifest, a label convention, a comment command.
Deep dive: Monorepo vs. Polyrepo for Preview Environments.
Step 11: AI agent context files
Check for CLAUDE.md, AGENTS.md, .cursorrules. Do they describe the preview environment? Seed data? Secret-loading conventions? If not, agent-generated PRs will keep stepping on these landmines.
Deep dive: The CLAUDE.md Guide for Preview-Aware Repositories.
What you have at the end
A list — probably 8-15 findings. A few P0s (PII in seeds, real third-party calls, hardcoded OAuth callback). A handful of P1s (missing seed, no migration on boot, flag defaults wrong). A long tail of “fine for now.”
Sort by leverage. Almost always: seed data first, env-var hygiene second, OAuth/webhooks third. Background jobs and feature flags are usually P2 — important, but not blocking the first useful preview. Most teams discover they’re closer than they thought. Three or four engineers, a long Friday, and you’re ready.
If you’d rather not run the audit yourself, PreviewProof does it as part of onboarding. We connect to the repo, run the patterns above, and report back with a prioritized list — and ship the platform that runs the previews once you’ve fixed the findings. Either way, the audit is the right starting move.