Ship/Now

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.