CI should confirm quality, not be the first place obvious problems appear. A good local setup catches missing environment variables, broken routes, type drift, bad seed data, and viewport-only layout bugs while the developer still has the change in their head.
The setup does not need to be elaborate. It needs to be repeatable and honest.
Make one command cover the common path
For a content-heavy Astro or Next project, a local check can be simple:
{
"scripts": {
"check:local": "pnpm lint && pnpm typecheck && pnpm validate:content && pnpm build && pnpm smoke"
}
}
The command should be boring enough to run before a commit. Slow browser suites can stay in CI, but route generation, content validation, and a few smoke checks should be local.
Seed awkward data, not perfect data
Local development often looks good because the data is too clean. Seed records should include one normal case, one minimal case, and one awkward case:
await seedArticle({
title: "A normal article title",
description: "A realistic summary for article cards.",
tags: ["frontend"],
});
await seedArticle({
title: "Long title that wraps across multiple lines on mobile cards",
description: "Short summary.",
tags: ["architecture", "frontend", "tooling"],
cover: null,
});
That second record catches UI and metadata assumptions that a perfect demo record hides.
Smoke test routes people actually open
A route smoke test does not need to click every control. It should prove that important pages render:
const routes = ["/", "/posts/", "/about/", "/privacy-policy/", "/rss.xml"];
for (const route of routes) {
const res = await fetch(`http://localhost:4321${route}`);
if (!res.ok) throw new Error(`${route} returned ${res.status}`);
}
If a content site has article pages, include one real article and one topic page. If it has account pages, include the simplest authenticated path in a separate test.
Print fixes, not just stack traces
Configuration failures should tell the developer what to do:
Missing PUBLIC_SITE_URL
Create .env.local from .env.example or set PUBLIC_SITE_URL=http://localhost:4321
The local tool should not require someone to understand the framework stack trace for a missing environment variable.
Mirror production where assumptions matter
Local development does not need to reproduce every production service. It should match the assumptions that can break the app: database major version, content schema, route generation, runtime boundaries, and environment variable names.
A mock email service is fine if it records the template, recipient, and payload. A local SQLite database may be fine for a prototype, but it will not catch Postgres enum migrations, case-sensitive indexes, or transaction behavior.
Write down the first failed hour
New contributors reveal setup truth quickly. If the first hour is spent guessing Node versions, seed commands, missing env vars, or port conflicts, update the setup guide.
A useful guide includes:
runtime versions
install command
env file source
seed command
dev command
local check command
first routes to open
reset command for local data
When local checks align with CI, developers stop treating CI as a distant judge. It becomes confirmation that the local loop already covered the common failure paths.