FAQ
Quick answers to the questions developers ask most when they first clone ShipVeryFast, package manager, which services you really need, env vars, Stripe modes, and where the database lives.
Required environment variables are validated at import time by libs/config.ts. If one is missing or malformed, the app throws on startup rather than failing silently later, so a clean boot means your config is valid.
Which package manager and Node version does this use?
ShipVeryFast uses npm. The repo ships a package-lock.json, and every script in the docs is written as npm run …. The install step also runs Husky git hooks via the prepare script (husky install), so a plain npm install sets up everything in one go.
git clone <your-repo-url> shipveryfast
cd shipveryfast
npm install # also runs "husky install" via the prepare script
cp .env.example .env.local
npm run dev # Next.js + Turbopack at http://localhost:3000It is a Next.js 15 (App Router) + React 19 project, so any actively supported Node LTS release works. The dev server runs with Turbopack (next dev --turbopack) and serves on http://localhost:3000.
Can I run it without Stripe, Supabase, or AI keys?
Partly. The two behave very differently:
- AI keys are fully optional.
ANTHROPIC_API_KEYandOPENAI_API_KEYare declared asz.string().optional()inlibs/config.ts. The AI assistant degrades gracefully,availableProviders()inlibs/ai/index.tsonly surfaces providers whose key is present, and the chat endpoint returns a503with a clear message if you call an unconfigured one. The rest of the app runs normally. - Supabase, Stripe, Mailgun and Google keys are required to boot. They are non-optional in the Zod schema, so the app throws at startup if they are missing. You can point them at free / test-tier accounts, that is enough to build and test auth, checkout and email end to end, but you cannot leave them blank and expect the server to start.
In short: you can run without spending money (test/free tiers), but not without the keys themselves. The AI layer is the only piece that truly runs with nothing configured.
Which env vars are truly required vs optional?
The source of truth is the envSchema in libs/config.ts, which calls envSchema.parse(process.env) at import time. Anything not marked .optional() there is required. Here is the snapshot.
Required vs optional env at a glance
| Variable | Required? | Zod rule / notes |
|---|---|---|
SUPABASE_URL | Required | z.string().url() |
SUPABASE_ANON_KEY | Required | z.string() |
SUPABASE_SERVICE_ROLE_KEY | Required | z.string() · server-only secret |
NEXTAUTH_SECRET | Required | z.string() · e.g. openssl rand -base64 32 |
NEXTAUTH_URL | Required | z.string().url() · set to your deployed URL in prod |
STRIPE_SECRET_KEY | Required | z.string() |
STRIPE_PUBLIC_KEY | Required | z.string() |
STRIPE_WEBHOOK_SECRET | Required | z.string() |
STRIPE_PRICE_BASIC | Required | z.string() |
STRIPE_PRICE_PRO | Required | z.string() |
STRIPE_PRICE_ENTERPRISE | Optional | z.string().optional() · "contact sales" tier |
MAILGUN_API_KEY | Required | z.string() |
MAILGUN_DOMAIN | Required | z.string() |
MAILGUN_FROM_EMAIL | Required | z.string().email() |
GOOGLE_CLIENT_ID | Required | z.string() · Google OAuth |
GOOGLE_CLIENT_SECRET | Required | z.string() · Google OAuth |
RATE_LIMIT_MAGICLINK_MAX | Required | z.coerce.number() · default 5 |
RATE_LIMIT_MAGICLINK_DURATION | Required | z.coerce.number() · default 3600 (seconds) |
ANTHROPIC_API_KEY | Optional | z.string().optional() · AI assistant (Claude) |
OPENAI_API_KEY | Optional | z.string().optional() · AI assistant (OpenAI) |
Two more keys appear in .env.example but are not in the Zod schema, so they are not validated at startup: CSRF_SECRET (used by CSRF protection) and ADMIN_EMAILS. Set them anyway, they gate real behavior. For the full annotated list, see Environment variables.
How do I switch Stripe from test to live mode?
Nothing in the code is hard-coded to a mode. Stripe is "in" test or live purely based on which keys and price IDs you put in the environment. To go live, swap four kinds of values in your production environment:
- Replace your test
STRIPE_SECRET_KEYandSTRIPE_PUBLIC_KEYwith the live keys from the Stripe Dashboard (Developers → API keys). - Recreate (or copy) your products/prices in live mode and update
STRIPE_PRICE_BASIC,STRIPE_PRICE_PROand (if used)STRIPE_PRICE_ENTERPRISEwith the live price IDs. These resolve throughgetStripePriceId()inlibs/pricingPlans.ts, so changing the env is all it takes, no code edits. - Create a live webhook endpoint pointing at your deployed app and set
STRIPE_WEBHOOK_SECRETto that endpoint's signing secret. Test-mode and live-mode webhook secrets are different. - Make sure
NEXTAUTH_URLis your real production URL so Checkout redirects and webhook origins line up.
While developing, keep using Stripe's test card 4242 4242 4242 4242 with any future expiry and any CVC. See Payments for the full Checkout and webhook flow.
How do I add a provider, model, dashboard page, or feature flag?
Add an AI model or provider
The AI layer lives in libs/ai/. Each provider (e.g. libs/ai/anthropic.ts) exports its model catalogue. To add a model to an existing provider, add an entry to its MODELS array, the streaming chat endpoint and the picker read it automatically:
// libs/ai/anthropic.ts
const MODELS = [
{ id: 'claude-opus-4-8', label: 'Claude Opus 4.8' },
{ id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
{ id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5' },
// add your new model id + label here
];To add a whole new provider, create a module that satisfies the Provider interface (an isConfigured() check plus an async streamChat() generator) and register it in the PROVIDERS map in libs/ai/index.ts. Because the UI is driven by providerCatalogue() / availableProviders(), it appears automatically once its key is set. See AI engine for the full contract.
Add a dashboard page
Dashboard navigation is data-driven in libs/dashboard/nav.ts. Create your route under app/dashboard/your-page/page.tsx, then add a NavItem to the relevant group in NAV_GROUPS (and a label in ROUTE_LABELS for breadcrumbs):
// libs/dashboard/nav.ts
{ href: '/dashboard/reports', label: 'Reports', icon: BarChart3 },The sidebar, command palette and breadcrumbs all read from this config, so one entry wires the page into the whole shell. More in Dashboard.
Add a feature flag
Feature flags are exposed through the FeatureFlagsProvider. Read them with the useFeatureFlags() hook (note the plural), which returns isEnabled and loading:
import { useFeatureFlags } from '@/components/providers/FeatureFlagsProvider';
const { isEnabled, loading } = useFeatureFlags();
return isEnabled('new-billing') ? <NewBilling /> : <OldBilling />;The admin dashboard at /admin also ships a visual feature flags manager. See Feature flags for managing flags and rollouts.
Where do migrations and SQL live, and how do I run the test DB?
SQL migrations live in supabase/migrations/. The boilerplate ships a single initial migration, 0001_initial_schema.sql, which creates the core tables: users, subscriptions, payments, email_verifications, password_resets and blog_posts (with Row Level Security). Apply it from the Supabase dashboard SQL editor or the Supabase CLI.
For local integration testing there is a throwaway Postgres container defined in docker-compose.yml (service db-test, postgres:15-alpine). It maps host port 5433 to container 5432, database shipveryfast_test, with user/password test/test:
npm run db:test:up # docker-compose up -d db-test (port 5433)
npm run test:integration
npm run db:test:down # docker-compose downFor schema details and the typed Zod models that mirror these tables, see Database.
Next steps
- Environment variables, the full annotated list with where to find each value
- Quickstart, clone, configure and boot in a few minutes
- Troubleshooting, fixes for common startup and config errors
