Import this GitHub repository into Vercel and go live without cloning locally first — you can wire env and domains in the Vercel dashboard after import.
Buttercut is a production-oriented personal-site theme for Next.js (App Router, React 19). One typed site.config.ts drives navigation, brand, home sections, and optional integrations; copy lives under content/demo/ as Markdown, JSON, and MDX. Clone it, run it, and you get a full portfolio out of the box: home, about, projects, notes, and an in-repo Guide from first clone to deployment. The default demo needs no API keys — optional Last.fm, weather, and GitHub integrations fail open when unconfigured, so pages stay stable.
It targets maintainability (registry-based blocks, explicit MDX note registration, tests that keep documentation accurate) and editorial polish (Nunito, Bitter, JetBrains Mono; light and dark; restrained motion).
- Deploy
- Why Buttercut
- What's included
- Live demo and stack
- Quick start
- Philosophy
- Project layout
- Configuration
- Documentation map
- Scripts and CI
- Forking and deployment
- Changelog
- Contributing and security
- License
- Real pages, not a promise. Home, About, Projects, Notes, and an in-repo Guide are already wired, styled, and tested — you are not staring at an empty layout wondering what to build first.
- It runs the moment you clone.
npm installandnpm run devare enough to see the whole experience. Integrations show gentle placeholders until you add credentials; nothing crashes the shell. - Configuration you can trust.
createSiteConfigmerges yoursite.config.tswithsrc/lib/config/defaults.ts, so you override only what is different and TypeScript helps you stay on track. - Copy lives where writers expect it. Hero body, projects, about sections, and note metadata sit under
content/demo/— tweak text without hunting throughsrc/appfor every string. - MDX notes that feel like a real publication. Notes are
.mdxwithexport const frontmatter, optional JSX, stable heading ids (github-slugger), and an optional Table of contents aligned with kaichen.dev note UX — add<TableOfContents />before the first##when you want it. - Documentation that does not lie. The Guide (
/guide) uses stable URL fragments; README and source share the same anchors viasrc/lib/guide/anchors.ts, with tests that catch broken links. - GPL-3.0-or-later. Fork it, adapt it, ship it; share improvements if you redistribute — same spirit as many personal-site and tool projects.
| Area | What you get |
|---|---|
| Home | Block-based layout: hero, status row (Now Playing + weather slots), demo projects with GitHub stars, integrations panel — all reorderable and toggleable via config. |
| About / Projects | Structured sections (education, experience, volunteering, focus) and project cards with tags and optional repo-linked stars. |
| Notes | MDX under content/demo/notes/, explicit registry in src/lib/demo/mdx-notes.ts, /notes/[slug] with an editorial header and prose scale. |
| Guide | Ten-step onboarding in MDX with permalinks — clone, config, content, short copy, theme, blocks, notes, integrations, deploy. |
| Theme | CSS variables plus optional brand.theme overrides sanitized at build time; presets (sunset, ocean, terminal). |
| Integrations | GitHub repo stars (on by default, unauthenticated quota), Last.fm, Open-Meteo weather — each optional and null-safe. |
- See it live: buttercut.kaichen.dev — tutorial links in this README point here; after you fork, you may swap in your own site URL while keeping the same
#anchorids. - Under the hood: Next.js 16, React 19, TypeScript (strict), Tailwind CSS 4, MDX via
@next/mdx, Vitest for unit and smoke tests.
Start with the tutorial: Step 1 — Clone and run.
git clone https://github.com/kaiiiichen/buttercut.git
cd buttercut
npm install
npm run devOpen http://localhost:3000. You do not need a .env file for the default demo.
- Simple changes stay in config. Title, nav, socials, block order, and theme tokens belong in
site.config.tsandcontent/demo/— not in a fork of half the theme. - Deep changes have a hook. Register or replace blocks in
src/custom/register.tsso upstream theme updates can still merge cleanly. - Integrations are optional by design. If an API key or username is missing, the UI explains the gap instead of throwing — the deployment still renders.
- Trusted author content. Inline markdown and MDX are for your copy in the repo, not arbitrary public user input. Treat
content/demolike source code.
| Path | Role |
|---|---|
site.config.ts |
Your overrides on top of defaults — site metadata, nav, socials, home.blocks, brand, integrations, content.allowedLinkSchemes. |
mdx-components.tsx |
Global MDX mapping: ButtercutProse wrapper, h2/h3 ids, TableOfContents tag name. |
src/app |
App Router routes: /, /about, /projects, /notes, /notes/[slug], /guide, /mdx-demo. |
src/blocks |
Home section components (ButtercutHero, ButtercutDemoProjects, …). |
src/components |
Shared UI: nav, theme, prose, GitHub activity, note TOC, etc. |
src/custom |
Your register.ts and optional block components — survives theme pulls if you keep changes here. |
src/lib/blocks |
Block registry API. |
src/lib/config |
Types, defaults, mergeSiteConfig. |
src/lib/integrations |
Fetch helpers for GitHub, Last.fm, weather. |
src/lib/demo |
Loaders for demo JSON/Markdown and the MDX note registry. |
src/lib/markdown |
inline.tsx for short copy (**, `, links) — auditable, no raw HTML dump. |
src/lib/guide |
Canonical /guide anchor list and reference tests. |
content/demo |
intro.md, about.json, projects.json, notes/*.mdx. |
public |
Avatars, OG placeholders, static assets. |
Walkthrough: Step 2 — Fill in site.config.ts.
Edit site.config.ts. Defaults live in src/lib/config/defaults.ts; merging is handled by mergeSiteConfig so partial objects are safe.
Guides: Step 6 — Reorder or hide home blocks · Step 7 — Add or override a block.
Built-in block ids include hero, status, demo_projects, integrations, now_playing, weather. Order and visibility come from home.blocks in config.
To replace or add a block without editing the theme core, use src/custom/register.ts:
import { registerButtercutBlock } from "@/lib/blocks/registry";
import { MyHero } from "./blocks/MyHero";
export function applyButtercutCustom(): void {
registerButtercutBlock("hero", MyHero);
}Each block receives { config, demo } where config is the merged site config and demo is loaded demo content.
Guide: Step 5 — Pick a colour mood.
Override CSS custom properties from config — values are sanitized before injection:
export const siteConfig = createSiteConfig({
brand: {
theme: {
accent: "#ff6f3c",
accentDark: "#ffa07a",
background: "oklch(99% 0 0)",
backgroundDark: "#111",
},
},
});Presets (src/lib/theme/presets.ts):
import { buttercutPreset } from "@/lib/theme/presets";
brand: {
theme: { ...buttercutPreset("sunset"), accent: "#ff3366" },
}| Preset | Mood |
|---|---|
sunset |
Warm editorial, paper-like light / deep warm dark. |
ocean |
Cool blues, calm corporate. |
terminal |
Monochrome green accent, night-friendly. |
Guide: Step 8 — Write notes in MDX.
- Routes under
src/app/**/*.mdxuse the global MDX wrapper (typography per route layout). - Demo notes live in
content/demo/notes/<slug>.mdx. Each file exportsfrontmatterwith at leasttitle, and usuallysummaryanddate. - Register every note in
src/lib/demo/mdx-notes.ts— the registry is the publish list; unregistered files do not get routes.
After the frontmatter (and any local components), before the first ##, you can add:
<TableOfContents />The /notes/[slug] page wraps content in <article id="note-content">. The TOC scans #note-content h2[id], h3[id] and only renders when there are at least two headings (MIN_HEADINGS = 2), matching the reference behaviour from kaichen.dev’s note TOC. Heading ids come from MDX h2/h3 components using github-slugger (Turbopack-friendly — no rehype-slug in next.config).
Guide: Step 9 — Turn on optional integrations.
| Integration | Default | Config keys | Environment |
|---|---|---|---|
| GitHub stars | on | integrations.github.enabled |
Optional GITHUB_TOKEN (higher API quota) |
| Last.fm | off | integrations.lastfm.enabled, username |
LASTFM_API_KEY |
| Weather | off | integrations.weather.enabled, lat, lon, label |
None (Open-Meteo) |
All fetch helpers return null on failure so the page always renders.
Guide: Step 3 — Swap the content in content/demo/.
| File | Purpose |
|---|---|
intro.md |
Home hero body (inline markdown subset). |
about.json |
Sections for /about — omit or empty arrays hide cards. |
projects.json |
Tagline and project list; repo can drive href and stars. |
notes/*.mdx |
Long-form notes; register each in mdx-notes.ts. |
Guide: Step 4 — Authoring short copy.
Used for hero intro, card summaries, note one-liners — implemented in src/lib/markdown/inline.tsx as real React nodes (no dangerouslySetInnerHTML for this path):
| Syntax | Output |
|---|---|
**bold** |
Strong emphasis |
`code` |
Inline code chip |
[label](url) |
Link; external URLs get rel/target |
Extend allowed URL schemes when you need tel: or sms::
content: {
allowedLinkSchemes: ["http", "https", "mailto", "tel", "sms"],
}A hard deny list always blocks javascript:, data:, vbscript:, and file:.
For block-level markdown (headings, lists, tables, fenced code), use MDX and ButtercutProse — not this inline helper.
| Layer | Where |
|---|---|
| Tutorial (step-by-step) | /guide — ten anchors from #clone-and-run to #deploy, defined in src/lib/guide/anchors.ts. |
| Reference (this file) | Feature overview, tables, and links into the Guide. |
| API detail | JSDoc on types (src/lib/config/types.ts) and integration helpers. |
Tutorial links in this README use https://buttercut.kaichen.dev/guide#.... After you fork, you may replace the origin with your own deployment; keep fragment ids aligned with anchors.ts so cross-links stay valid.
| Script | Command | Purpose |
|---|---|---|
dev |
next dev |
Local development |
build |
next build |
Production build |
start |
next start |
Serve production output |
lint |
eslint . |
Lint |
typecheck |
tsc --noEmit |
Typecheck |
test |
vitest run |
Unit and integration tests |
test:smoke |
vitest run --dir src/app/guide |
Post-build smoke: /guide anchors in HTML |
CI runs: lint → typecheck → test → build → test:smoke.
Guide: Step 10 — Deploy.
Buttercut is a standard Next.js app — use the Deploy section above for a one-click Vercel import, or deploy on Vercel manually or on any Node host. Set site.siteUrl in site.config.ts to your production URL for metadata and OG. Replace demo placeholder copy in content/demo/ and adjust nav / socials to match your identity.
Human-readable history: CHANGELOG.md (Keep a Changelog). Current package version: 0.2.0.
- Contributing: CONTRIBUTING.md — local setup, full CI command list, and how to keep README, Guide, and code in sync.
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md — how to report issues affecting the theme.
GPL-3.0-or-later. Same license family as the kaichen.dev reference implementation.