Start here · 3 min
Configuration
One file holds every decision about your product. Everything else reads from it.
src/config.ts is the only file you have to edit to turn the kit into your
product. The nav, the page titles, the emails, the OG image and the plan names
all read from it.
export const config = {
name: "Acme",
domain: "acme.com",
description: "The thing you are building, in one sentence.",
email: {
from: "Acme <hello@acme.com>",
support: "hello@acme.com",
},
locales: ["en", "fr"] as const,
defaultLocale: "en",
billing: {
currency: "eur",
plans: [
{ id: "starter", name: "Starter", priceId: "price_starter_replace_me" },
{ id: "pro", name: "Pro", priceId: "price_pro_replace_me" },
],
},
brand: {
ink: "#0b0c0f",
accent: "#f8430a",
font: "Host Grotesk",
},
} as const;Decisions, not secrets
The split matters. config.ts is committed and readable by anyone who has your
code. .env is not committed and holds anything that would be dangerous in
someone else's hands.
A Stripe price id is a decision: it is public, it appears in the checkout
URL, and it belongs here. A Stripe secret key is a secret and belongs in
.env. If you find yourself wanting to put a key in config.ts, that is the
signal you have the wrong file open.
The as const
config is declared as const so the types narrow to the literal values. That
is what makes this work:
export type PlanId = Config["billing"]["plans"][number]["id"];
// "starter" | "pro"Add a plan to the array and PlanId widens automatically. Every function that
takes a plan id now refuses the ones that do not exist, at compile time, with
no list to keep in sync. Remove the as const and you get string, and that
guarantee quietly disappears.
Changing the brand
brand.accent is read by src/app/globals.css as the accent token. Change it
there and it changes everywhere: buttons, focus rings, the active nav item, the
OG image. See Design tokens.
brand.font is the display face. It is loaded through next/font/google in
src/app/layout.tsx, so changing it means changing both the config value and
the import, since next/font needs a static name at build time.
Dropping a language
Set locales: ["en"] as const and delete messages/fr.json. Nothing else has
to change: the switcher renders whatever is in locales, and with one entry it
renders nothing at all. See Internationalisation.
Something wrong or missing on this page? Tell us.