Going To Production
Pre-launch checklist for product-specific work (brand, copy, legal, live Stripe, email, OAuth). Infrastructure is wired; each category links to its subsystem page rather than duplicating it.
Last updated on
12 min readOn This Page
At A Glance
| Category | Touch this |
|---|---|
| Branding and identity | packages/brand/src/index.ts, packages/ui/src/components/logo.tsx, apps/web/app/opengraph-image.tsx |
| Marketing copy | apps/web/messages/en.json (and every other locale you ship) |
| Marketing structure | apps/web/config/marketing.ts |
| Legal pages | apps/web/content/legal/{en,de}/{privacy,terms,license}.mdx (driven by brand.legal in packages/brand/src/index.ts) |
| Pricing and plans | packages/payments/src/catalog/declaration.ts, plus Stripe live-mode products, prices, and webhook |
| Email sender | Verified domain, EMAIL_FROM, provider API key, CONTACT_FORM_TO_EMAIL |
| Internationalization | packages/i18n/src/config.ts and apps/web/messages/<locale>.json for every locale you ship |
| SEO and metadata | brand.seo in packages/brand/src/index.ts, apps/web/app/sitemap.ts, apps/web/app/robots.ts |
| Operational env | Security pre-launch checklist, Deployment |
Categories can run in any order. Stripe live mode is a short sequence inside its section. The smoke test is the gate before you open to users.
Branding And Identity
Edit the brand object in packages/brand/src/index.ts once. Site metadata, the OG image, email templates, legal MDX placeholders, and the Logo wordmark all read from it.
Update the brand config
In packages/brand/src/index.ts, set name, description, wordmark.primary / wordmark.accent, urls.* (app, docs, pricing), social.*, the full legal.* block (entity, address, governing law, contact and DPO emails, EU representative, arbitration body, currency, payment grace days, effective date), and seo.keywords / seo.author.
Replace the wordmark
packages/ui/src/components/logo.tsx reads brand.wordmark.primary and brand.wordmark.accent. Change those fields for text. Set accent to "" for a monochrome wordmark. For an SVG or <Image>, edit the JSX in Logo. Headers, footers, sidebars, and auth screens pick it up automatically.
Verify the OG image
apps/web/app/opengraph-image.tsx and social-image.tsx read brand.name, brand.wordmark.*, and brand.description. Most rebrands need no generator edits. Change the generator only if you want custom artwork.
Replace the favicon and icons
The kit ships apps/web/app/icon.svg and a generated apple icon (apps/web/app/apple-icon.tsx, mark from @syntaxkit/ui). Replace those with your mark, or drop in static icon.png / apple-icon.png and remove the generated routes. Optional: add apps/web/app/manifest.ts. Next.js discovers these with no extra config.
Marketing Copy And Structure
Put your strings in apps/web/messages/en.json (shape is type-checked; wording is yours). Then adjust layout in apps/web/config/marketing.ts:
marketing.ts field | Controls |
|---|---|
sections | Which homepage sections render |
heroConfig.primaryCta / secondaryCta | Signup and contact destinations |
logos | Built-with trust bar |
features | Icon feature grid (ids map to message keys) |
featureHighlights | Alternating image + text rows (imageSrc under apps/web/public/images/) |
faqIds | Which FAQ items render |
footerLinks | Footer destinations |
socialLinks | X and GitHub (or remove either) |
Overwrite every customer-facing string
Replace the kit copy in apps/web/messages/en.json. apps/web/global.d.ts types messages from that file, so missing keys fail the build.
Translate every locale you ship
Repeat for every other locale (the kit ships en and de). See Internationalization for the locale registry.
The shipped strings still sound like a starter kit ("production-ready", "ship faster"). Read every key in apps/web/messages/en.json before launch. That file owns copy; marketing.ts owns structure.
Legal Pages
Legal MDX interpolates brand.legal values (entity, address, governingLaw, contact and DPO emails, EU representative, arbitration body, and related fields) into realistic-looking placeholders. They are not counsel-reviewed. Update the config and have qualified counsel review the templates before a public launch.
Update the brand legal block
Replace every value under brand.legal in packages/brand/src/index.ts with your registered entity. Values flow into /privacy, /terms, and /license in every locale through <Entity />, <Address />, <Jurisdiction />, <ContactEmail />, <DpoEmail />, <EuRepresentative />, <ArbitrationBody />, <Currency />, and <PaymentGraceDays /> in apps/web/content/legal/{en,de}/.
Tailor the legal MDX bodies
The templates are not jurisdiction-specific. Edit apps/web/content/legal/{en,de}/{privacy,terms,license}.mdx for your processing, subprocessors, retention, dispute resolution, and payment terms. Have counsel review before real users see them.
Decide what to do about the license page
/license ships in both locales (apps/web/app/[locale]/(marketing)/license/page.tsx). If you do not license redistributable IP, remove the license entry from footerLinks in apps/web/config/marketing.ts, then delete apps/web/app/[locale]/(marketing)/license/ and the matching MDX files.
Add a cookie banner if you process EU traffic
The kit ships consent helpers (optOut, optIn, hasOptedOut from @syntaxkit/analytics/client) but no banner UI. See Analytics and wire the banner to your CMP.
Pricing And Stripe Live Mode
Work this section top to bottom in the Stripe Dashboard and your env.
Create live-mode products and prices
Switch to live mode (top-right toggle). Create one product each for Pro and Ultra, with monthly and yearly prices in your production currency. Free is the catalog baseline and needs no Stripe price.
Set the price-id env vars
Copy each live price_* id into:
STRIPE_PRICE_ID_PRO_MONTHLYSTRIPE_PRICE_ID_PRO_YEARLYSTRIPE_PRICE_ID_ULTRA_MONTHLYSTRIPE_PRICE_ID_ULTRA_YEARLY
The billing catalog derives required vars from packages/payments/src/catalog/declaration.ts. The app fails at startup if any declared price env is missing.
Update the billing catalog to match Stripe
In packages/payments/src/catalog/declaration.ts, update each paid plan (pro, ultra): name, description, entitlements, trialDays, and displayed amount / currency per price. Feature bullets come from entitlements (and BillingFeatures messages), not a separate features list. Stripe owns billed amounts; a stale catalog amount makes the pricing page lie.
Verify the catalog matches Stripe
Run pnpm billing:check-prices with your Stripe keys and STRIPE_PRICE_ID_* set. It fails on amount or currency drift, archived Prices, or missing Prices (zero-decimal currencies like JPY handled correctly). The scheduled stripe-live CI workflow runs the same check. It is a build/dev-time guard only (no Stripe call on render paths) and skips cleanly when billing is not configured.
Register the production webhook
Add an endpoint at <NEXT_PUBLIC_APP_URL>/api/webhooks/stripe. Subscribe to the nine events on Webhooks And Async Workflows: checkout.session.completed, customer.subscription.{created, updated, deleted, trial_will_end}, invoice.{finalized, payment_action_required, payment_failed, payment_succeeded}.
Set the production secrets
Put the production webhook signing secret (not the Stripe CLI one) in STRIPE_WEBHOOK_SECRET, and your sk_live_* key in STRIPE_SECRET_KEY. Both are runtime env, not build args.
Enable the payment methods you want
In Settings → Payment methods, enable cards plus any wallets (Apple Pay, Google Pay, Link), Buy Now Pay Later, and regional methods (SEPA, iDEAL, Bancontact, and similar). The kit does not pin payment_method_types, so Dashboard toggles apply to new Checkout sessions without a redeploy.
Activate Stripe Tax (if you need VAT, GST, or sales tax)
In Settings → Tax, activate Stripe Tax and add a tax registration for each obligation. Then set STRIPE_AUTOMATIC_TAX="true" in runtime env. Checkout collects billing addresses, shows a VAT / tax-id field for B2B buyers, and computes tax. Leave the flag unset until at least one registration exists, or Stripe rejects every Checkout session.
The pricing table reads the catalog. Webhooks drive subscription state. Keep them aligned with pnpm billing:check-prices and the stripe-live CI job so the site never shows one price while Stripe charges another.
Email Sender Configuration
Point the kit at your provider, set the env vars, and smoke-test every template before launch.
Verify your sending domain
Set EMAIL_FROM
Set the runtime env vars
Set the production provider key (for Plunk: PLUNK_API_KEY), EMAIL_FROM, and CONTACT_FORM_TO_EMAIL (where public contact-form submissions land). Production rejects EMAIL_DELIVERY_MODE=log and noop at boot.
Smoke-test every template
From staging, trigger one of each: verification, password reset, organization invitation, subscription created, payment failed. Open each in the recipient inbox and confirm rendering, sender display, and deliverability before real users.
Internationalization
Pick locales, translate every key, and decide what search engines can crawl.
Decide which locales you ship
Edit packages/i18n/src/config.ts (locales and localeLabels). Defaults are en and de. Trim or add as needed.
Translate every key per locale
For every locale you keep, apps/web/messages/<locale>.json must include every key from en.json. The TypeScript build catches missing keys.
Audit indexability
apps/web/proxy.ts redirects unprefixed marketing routes to the preferred locale via the NEXT_LOCALE cookie. Remove a locale from the registry if you do not want it crawled. Full walkthrough: Internationalization.
SEO And Metadata
Swap starter-kit metadata for yours, then register the sitemap.
Replace SEO keywords and author
Update brand.seo.keywords and brand.seo.author in packages/brand/src/index.ts. apps/web/app/layout.tsx reads those defaults. Replace starter-kit terms ("SaaS starter kit", "Next.js", "boilerplate") with terms for your product.
Extend the sitemap
Add new marketing routes to the staticRoutes array in apps/web/app/sitemap.ts. The helper emits locale-aware entries with the right alternates.languages block.
Audit robots.txt
The default disallow list in apps/web/app/robots.ts covers /auth/, /dashboard/, /api/, /rpc/, /api-reference/, /accept-invitation/, /create-organization/, plus locale-prefixed auth and onboarding paths. That is correct for almost every SaaS. Change it only if your product has a non-standard public surface.
Verify the canonical app URL
Confirm NEXT_PUBLIC_APP_URL matches the canonical production origin. CSP origins, CORS allow-origin, sitemap entries, and OG image URLs all derive from it.
Submit the sitemap
Submit <NEXT_PUBLIC_APP_URL>/sitemap.xml to Google Search Console and any other search engines that matter to you.
Operational Environment Variables
The full env-var matrix lives on Environment Variables. Production-specific concerns each have one home:
| Concern | Where the checklist lives |
|---|---|
| Auth secrets, abuse protection, captcha, multi-instance encryption key | Security pre-launch checklist |
| Per-host build-arg vs runtime env split | Deployment build-time vs runtime env |
| Storage bucket configuration (CORS, lifecycle rules) | Storage required bucket configuration |
| Source map upload secrets | Monitoring source maps |
| OAuth callback URLs in GitHub / Google consoles | Security pre-launch checklist |
Pre-Launch Checklist
Work the categories above, then tick them off here. Progress saves to this browser's localStorage.
Before You Deploy
Smoke Test After First Deploy
Run this against the live production deploy. Anything that fails goes back to its subsystem page before you open to users. Progress persists in this browser.
After Your First Deploy
Where To Go Next
Deployment
Per-host setup for the env vars and webhook URLs you collected here.
Security
Pre-launch security checklist: encryption keys, abuse protection, OAuth callbacks.
Also useful: Setup for the env-var matrix, Webhooks And Async Workflows for the Stripe events table, and Monitoring for source-map upload and post-launch visibility.
Deployment
Deploy apps/web and apps/docs with Next.js standalone output. Run prisma migrate deploy before the new version takes traffic. Configs ship for Vercel, Fly.io, Render, and Docker Compose.
Webhooks And Async Workflows
One inbound Stripe webhook with signature verification, two-layer idempotency, and stale-claim recovery in Postgres. No background queue ships; after(), OutboundEffect, and common providers to plug in are below.
