quiet on setv1.0.1

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-app

Leave 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 --clone

Install and run

cd my-app
pnpm install
pnpm dev

The dev server runs at http://localhost:5173.

Scripts

CommandWhat it does
pnpm devDev server at http://localhost:5173
pnpm buildProduction build to build/ (Node server)
pnpm startServe the production build
pnpm build:spaStatic SPA build to build/client, no server
pnpm preview:spaServe the SPA build at http://localhost:4173
pnpm fmtFormat everything
pnpm testRun the tests once (pnpm vitest to watch)
pnpm checkFormat check, lint, typecheck and tests
pnpm typecheckGenerate route types and run tsc
pnpm faviconRebuild 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.

VariableDefaultEffect
SPAunsettrue builds a static single-page app (ssr: false) instead of the Node server build
VITE_*noneExposed 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

  1. Rename the project in package.json and update the brand in app/welcome/welcome.tsx.
  2. Reset versioning: set version in package.json to your starting point (for example 0.1.0) and delete CHANGELOG.md. Your copy has none of the template's tags, so release-please starts fresh.
  3. Swap the hero image (Wardrobe has the details).
  4. Add routes and components (Blocking and Props).
  5. Replace the favicon. Edit assets/favicon-tile.svg, then run pnpm 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:…). Only feat: and fix: 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 bumps version in package.json and the degit command in the README, updates CHANGELOG.md, tags vX.Y.Z and publishes a GitHub Release.
  • Settings are in release-please-config.json. .release-please-manifest.json records 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.