Going To Production
Pre-launch checklist and hardening steps.
Last updated on
11 min readSyntaxKit's infrastructure (auth, billing, abuse, monitoring, deployment) is done and battle-tested. What's left before launch is everything specific to your product: brand, copy, legal text, live Stripe prices, the email domain, OAuth callback URLs. This page is that checklist; each category links to its subsystem page rather than duplicating it.
Pricing & Stripe live mode
Live products, price-id env vars, the webhook, payment methods, and tax.
Legal pages
Brand legal block, MDX bodies, the license page, and EU cookies.
Pre-launch checklist
Every launch task as a saved, tickable checkbox.
Smoke test
What to verify against the live deploy before opening up.
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 the legal block in packages/brand/src/index.ts) |
| Pricing and plans | packages/payments/src/catalog/declaration.ts, plus Stripe live-mode products / prices / webhook |
| Email sender | Provider dashboard (verified domain, sender identity) plus env vars |
| Internationalization | apps/web/messages/<locale>.json for every locale you ship |
| SEO and metadata | apps/web/app/layout.tsx, apps/web/app/sitemap.ts |
| Operational env | Security pre-launch checklist, Deployment |
Order doesn't matter. Do the categories in whichever sequence fits your team. The smoke test is the gate.
Branding And Identity
One edit to the brand object in packages/brand/src/index.ts cascades through the site metadata, the OG image, every email template, the legal MDX bodies, and the Logo wordmark.
Update the brand config
Edit the single brand object in packages/brand/src/index.ts: name, description, the wordmark split (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 the seo.keywords / seo.author defaults.
Replace the wordmark
The shipped Logo (packages/ui/src/components/logo.tsx) reads brand.wordmark.primary and brand.wordmark.accent directly. Update those fields for a text change (set accent to an empty string for a monochrome wordmark). For richer artwork (an inline SVG, an <Image>), edit the JSX inside Logo; every header, footer, sidebar, and auth surface picks it up automatically.
Verify the OG image
apps/web/app/opengraph-image.tsx and the social-image.tsx generator read brand.name, brand.wordmark.*, and brand.description, so most rebrands work once the config is updated. Edit the generator if you want custom artwork.
Add a favicon and icons
The kit ships none. Drop in apps/web/app/icon.png, apps/web/app/apple-icon.png, and (optional) apps/web/app/manifest.ts. Next.js auto-discovers these with no extra config.
Marketing Copy And Structure
Replace the kit's copy with yours in apps/web/messages/en.json (the shape is fixed and type-checked; the strings are not), then adjust the layout in apps/web/config/marketing.ts:
marketing.ts field | Controls |
|---|---|
sections | Which homepage sections render |
heroConfig.primaryCta / secondaryCta | Your real signup / contact endpoints |
logos | The "built-with" trust bar |
featureHighlights | The alternating image+text rows (and their imageSrc paths under apps/web/public/images/) |
faqIds | How many FAQ items render |
footerLinks | Footer destinations |
socialLinks | X and GitHub links (or remove either) |
Overwrite every customer-facing string
Replace the kit's copy in apps/web/messages/en.json. The TypeScript declaration in apps/web/global.d.ts rejects missing keys at build time, so you can't ship a half-translated file.
Translate every locale you ship
Repeat the copy step for every additional locale (the kit ships en and de). See Internationalization below for the locale registry.
The kit's copy makes starter-kit claims ("production-ready", "ship faster") that don't fit your product. Read every string in apps/web/messages/en.json before launch: the file is the single source of truth; everything else is structure.
Legal Pages
The shipped legal pages interpolate company values (entity, address, governingLaw, contact and DPO emails, EU representative, arbitration body, …) from packages/brand/src/index.ts into realistic-looking placeholders, not counsel-reviewed text. Update the config and have qualified counsel review the templates before launching publicly.
Update the brand legal block
Replace every value under brand.legal in packages/brand/src/index.ts with your registered entity. The values flow into /privacy, /terms, and /license in every locale via the <Entity />, <Address />, <Jurisdiction />, <ContactEmail />, <DpoEmail />, <EuRepresentative />, <ArbitrationBody />, <Currency />, and <PaymentGraceDays /> placeholders in apps/web/content/legal/{en,de}/.
Tailor the legal MDX bodies
The templates aren't jurisdiction-specific. Edit apps/web/content/legal/{en,de}/{privacy,terms,license}.mdx to match your processing, subprocessor list, retention windows, dispute-resolution clauses, and payment terms, with qualified counsel review before you point real users at them.
Decide what to do about the license page
/license ships in both locales (apps/web/app/[locale]/(marketing)/license/page.tsx). If your product is hosted-only and you don't license redistributable IP, remove the license entry from footerLinks in apps/web/config/marketing.ts and delete apps/web/app/[locale]/(marketing)/license/ plus 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 build the banner against whatever CMP you choose.
Pricing And Stripe Live Mode
This one is a genuine sequence: work it top to bottom in the Stripe dashboard and your env.
Create live-mode products and prices
Switch to live mode (top-right toggle), then create one product per plan and matching monthly and yearly prices in your production currency.
Set the price-id env vars
Copy each price_* id into STRIPE_PRICE_ID_PRO_MONTHLY and STRIPE_PRICE_ID_PRO_YEARLY. The billing catalog derives the required vars from your plan declaration, and the app fails at startup if either is missing. A misconfigured deploy fails fast.
Update the billing catalog to match Stripe
In packages/payments/src/catalog/declaration.ts, update the pro plan (name, description, features, trialDays, and the displayed amount / currency per price). Stripe stays the source of truth for billed amounts, but a stale amount against a different Stripe price 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 if the catalog's amount / currency disagrees with the live Price, or if a Price is archived or missing (zero-decimal currencies like JPY handled correctly). The scheduled stripe-live CI workflow runs the same check. It's a build/dev-time guard only (no Stripe call on any render path) and skips cleanly when billing isn't configured.
Register the production webhook
Add an endpoint at <NEXT_PUBLIC_APP_URL>/api/webhooks/stripe and subscribe to the nine events documented 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 (different from the dev / 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, toggle cards plus any wallets (Apple Pay, Google Pay, Link), Buy Now Pay Later, and regional methods (SEPA, iDEAL, Bancontact, …). The kit doesn't pin payment_method_types, so each toggle applies to every new Checkout session without a redeploy.
Activate Stripe Tax (if you sell into VAT / GST / sales-tax jurisdictions)
In Settings → Tax, activate Stripe Tax and add a tax registration for each obligation, then set STRIPE_AUTOMATIC_TAX="true" in runtime env. Checkout then collects billing addresses, surfaces the VAT / tax-id field for B2B buyers, and computes tax automatically. Leave the flag unset until at least one registration exists, or Stripe rejects every Checkout session.
The pricing table reads from the catalog; the webhook events drive subscription state regardless. Keeping the two in sync (enforced by pnpm billing:check-prices and the stripe-live CI job) is what prevents "the site said $29 but I was charged $49" tickets.
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 the sender identity
Configure the sender identity (e.g. support@yourdomain.com) in the provider dashboard. The kit's sendEmail helper does not pass a from field: the provider derives it from the dashboard sender. To swap off Plunk, see Email.
Set the runtime env vars
Set PLUNK_API_KEY to your provider's production API key and CONTACT_FORM_TO_EMAIL to where public contact-form submissions should land.
Smoke-test every template
From a staging environment, 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 opening to real users.
Internationalization
Pick your locales, translate every key, and decide what's crawlable.
Decide which locales you ship
Edit packages/i18n/src/config.ts to set locales and localeLabels. The kit defaults to en and de. Trim what you don't need or add what you do.
Translate every key per locale
For every locale you keep, ensure apps/web/messages/<locale>.json has every key from en.json. The TypeScript build catches missing keys before they reach production.
Audit indexability
apps/web/proxy.ts redirects unprefixed marketing routes to the user's preferred locale via the NEXT_LOCALE cookie. Remove a locale from the registry if you don't want it crawled. Full walkthrough: Internationalization.
SEO And Metadata
Swap the starter-kit metadata for yours and register your sitemap.
Replace the keywords array
The defaults in apps/web/app/layout.tsx are starter-kit terms ("SaaS starter kit", "Next.js", "boilerplate"). Replace them with terms specific to your product.
Extend the sitemap
Append any new marketing routes to the staticRoutes array in apps/web/app/sitemap.ts. The helper auto-generates locale-aware entries with the right alternates.languages block.
Audit robots.txt
The default disallow list in apps/web/app/robots.ts (/auth, /dashboard, /api, /rpc, /accept-invitation, /create-organization) is correct for almost every SaaS. Only touch it if your product has a non-standard auth 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: a mismatch breaks several things in non-obvious ways.
Submit the sitemap
Submit <NEXT_PUBLIC_APP_URL>/sitemap.xml to Google Search Console and any other search engines that matter to your audience.
Operational Environment Variables
The full env-var matrix lives on Environment Variables. Production-specific concerns each have one canonical 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 is saved to this browser's localStorage, so you can pick up where you left off.
Before You Deploy
Smoke Test After First Deploy
The gate. 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
Setup
The full env-var matrix every category on this page references.
Security
The pre-launch security checklist that complements this page (encryption keys, abuse protection, OAuth callbacks).
Deployment
Per-host setup for the env vars and webhook URLs you collected here.
Webhooks And Async Workflows
The Stripe events table referenced from the live-mode webhook step.
Monitoring
Post-launch error tracking and log visibility, including the source-map upload step.
