Call Sheet
Getting started
quiet-on-set is a starting point for new web projects: React Router in framework mode, Tailwind CSS v4 and shadcn/ui on Base UI, with formatting, linting, tests and CI already set up. You copy it, rename it, and start on the actual site.
Scaffold a project
Copy a pinned release with degit. It takes the files from that release without any git history, so your project starts with a clean repo.
npx degit felton/quiet-on-set#v1.0.1 my-appLeave off #v1.0.1 to get the latest main. Every version is listed on the Releases page.
You can also click Use this template on GitHub, or do the same from the command line. Both copy the current main:
gh repo create my-app --template felton/quiet-on-set --private --cloneInstall and run
cd my-app
pnpm install
pnpm devThe dev server runs at http://localhost:5173.
Whichever tool ran degit, install with pnpm. The version is pinned in the packageManager field
of package.json, and CI installs from pnpm-lock.yaml. If you don't have pnpm, corepack enable sets it up.
Scripts
| Command | What it does |
|---|---|
pnpm dev | Dev server at http://localhost:5173 |
pnpm build | Production build to build/ (Node server) |
pnpm start | Serve the production build |
pnpm build:spa | Static SPA build to build/client, no server |
pnpm preview:spa | Serve the SPA build at http://localhost:4173 |
pnpm fmt | Format everything |
pnpm test | Run the tests once (pnpm vitest to watch) |
pnpm check | Format check, lint, typecheck and tests |
pnpm typecheck | Generate route types and run tsc |
pnpm favicon | Rebuild public/favicon.ico from its SVG |
pnpm check is the same command CI runs. Run it before calling anything done.
Testing
Tests sit next to the code they cover, as *.test.ts(x) files under app/. They run in jsdom with Testing Library. app/routes/home.test.tsx is the pattern to copy. It renders the route inside createRoutesStub, because route components use <Link> and <Link> needs a router:
const Stub = createRoutesStub([{ path: "/", Component: Home }]);
render(<Stub initialEntries={["/"]} />);
expect((await screen.findByRole("heading", { level: 1 })).textContent).toBe("Places, everyone.");Vitest has its own vitest.config.ts, because the React Router Vite plugin doesn't run under Vitest. vitest.setup.ts cleans up the DOM between tests.
Environment variables
Copy .env.example to .env for local values. .env is gitignored and left out of Docker images, and the React Router CLI loads it for dev, build and typegen.
| Variable | Default | Effect |
|---|---|---|
SPA | unset | true builds a static single-page app (ssr: false) instead of the Node server build |
VITE_* | none | Exposed to browser code as import.meta.env.VITE_*. They end up in the bundle, so never put secrets in them |
You can set SPA three ways: pnpm build:spa for one build, SPA=true pnpm build (the same thing, by hand), or SPA=true in .env for every command, including dev. With it in .env, plain pnpm build produces the SPA build too.
Make it yours
- Rename the project in
package.jsonand update the brand inapp/welcome/welcome.tsx. - Reset versioning: set
versioninpackage.jsonto your starting point (for example0.1.0) and deleteCHANGELOG.md. Your copy has none of the template's tags, so release-please starts fresh. - Swap the hero image (Wardrobe has the details).
- Add routes and components (Blocking and Props).
- Replace the favicon. Edit
assets/favicon-tile.svg, then runpnpm favicon.
Releases
Releases are automated with release-please, and the same workflow versions your project once you've reset it (step 2 above).
- Write commit messages as Conventional Commits (
feat:,fix:,chore:…). Onlyfeat:andfix:commits, or a!breaking change, trigger a release, so squash-merge PRs with a conventional title. - On each push to
main, release-please opens or updates a release PR. Merging it bumpsversioninpackage.jsonand the degit command in the README, updatesCHANGELOG.md, tagsvX.Y.Zand publishes a GitHub Release. - Settings are in
release-please-config.json..release-please-manifest.jsonrecords the last released version, and the release PR updates it. - The workflow uses the built-in
GITHUB_TOKEN, so no secret is needed. Each repo does need Settings → Actions → General → Allow GitHub Actions to create and approve pull requests turned on.
If you don't want automated releases, delete .github/workflows/release-please.yml.
With a coding agent
Give the agent the degit command and a brief for the site. AGENTS.md (which CLAUDE.md imports) tells it the commands, the layout and the conventions, and .agents/skills/ has the official React Router and shadcn/ui skills. Wrap has more.