Skip to content
Docs just relaunched - explore the new sidebar, OG images, and AI-ready content.
Operate And Ship

Going To Production

Pre-launch checklist and hardening steps.

Last updated on

11 min read

SyntaxKit'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.

At A Glance

CategoryTouch this
Branding and identitypackages/brand/src/index.ts, packages/ui/src/components/logo.tsx, apps/web/app/opengraph-image.tsx
Marketing copyapps/web/messages/en.json (and every other locale you ship)
Marketing structureapps/web/config/marketing.ts
Legal pagesapps/web/content/legal/{en,de}/{privacy,terms,license}.mdx (driven by the legal block in packages/brand/src/index.ts)
Pricing and planspackages/payments/src/catalog/declaration.ts, plus Stripe live-mode products / prices / webhook
Email senderProvider dashboard (verified domain, sender identity) plus env vars
Internationalizationapps/web/messages/<locale>.json for every locale you ship
SEO and metadataapps/web/app/layout.tsx, apps/web/app/sitemap.ts
Operational envSecurity 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 fieldControls
sectionsWhich homepage sections render
heroConfig.primaryCta / secondaryCtaYour real signup / contact endpoints
logosThe "built-with" trust bar
featureHighlightsThe alternating image+text rows (and their imageSrc paths under apps/web/public/images/)
faqIdsHow many FAQ items render
footerLinksFooter destinations
socialLinksX 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.

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

In your provider's dashboard (Plunk, Resend, AWS SES, or whichever you swap in), verify the sending domain and add DKIM + SPF DNS records, plus DMARC when you can. Without these, deliverability into Gmail and Outlook drops sharply.

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:

ConcernWhere the checklist lives
Auth secrets, abuse protection, captcha, multi-instance encryption keySecurity pre-launch checklist
Per-host build-arg vs runtime env splitDeployment build-time vs runtime env
Storage bucket configuration (CORS, lifecycle rules)Storage required bucket configuration
Source map upload secretsMonitoring source maps
OAuth callback URLs in GitHub / Google consolesSecurity 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

Was this page helpful?

On this page