{"slug": "3-layers-of-code-review-on-next-js-typescript-i-cut-human-review-time-from-4h-to", "title": "3 Layers of Code Review on Next.js + TypeScript: I Cut Human Review Time From 4h to 25min per PR", "summary": "A developer cut human code review time from four hours to 25 minutes per pull request by implementing a three-layer review system on a Next.js 15 + TypeScript + Prisma project. The layers use Biome and husky for mechanical checks, CodeRabbit and Copilot for pattern-level review, and the developer for design and business rule decisions. The developer found that 70% of their review comments could have been automated.", "body_md": "I was spending about four hours per pull request on review. Not on hard PRs — on all of them. Prisma model tweak? Half an hour of comments about naming. Server Action refactor? An hour of \"hey, missing `use client`\n\n\" back-and-forth. New Repository class? Ninety minutes because I was reading through 12 similar files to double-check the pattern.\n\nI ran a small audit on my own comments across a month. About 70% of what I wrote could have been said by a tool. Types, naming, format, \"you forgot a test\", \"don't use `any`\n\n\". Only 30% touched actual design decisions.\n\nSo I split review into three layers, wired them up on a Next.js 15 + TypeScript + Prisma project, and the human portion dropped to about 25 minutes per PR. Below are the actual configs.\n\n```\nLayer 1: Biome + husky (pre-commit, pre-push) — mechanical\nLayer 2: CodeRabbit + Copilot on PR open       — pattern-level\nLayer 3: Me                                    — design + business rule\n```\n\nEach layer has an explicit job. Layer 1 refuses to let mechanical noise reach Layer 2. Layer 2 refuses to let pattern-level noise reach Layer 3. Layer 3 only fires when the diff has already survived the first two.\n\nThat's the whole idea. The rest is config files.\n\nBefore any config: the file all three layers point to.\n\n```\n# AGENTS.md\n\n## Project\n\nNext.js 15 + TypeScript + Prisma + PostgreSQL\n\n## Architecture\n\n- App Router (Server Component default)\n- Repository Pattern (data access via Prisma)\n- Service layer (business logic)\n- Result<T, E> for error handling — do not throw\n\n## Code Direction\n\nIncrease:\n- Repository Pattern → src/repositories/*-repository.ts\n- Result<T, E> → src/types/result.ts\n- Data fetching inside Server Components\n- Prisma.select for narrow column lists\n\nDecrease:\n- any (use unknown)\n- Business logic in Route Handlers\n- throw for control flow\n- Client Component fetching (unless SWR/React Query)\n- Files over 300 lines\n\n## Review Policy\n\n- Conventional Comments syntax\n- Approval bar: codebase health improves = approve\n- First-response SLA: 24h\n```\n\nCodeRabbit reads this. Copilot reads this. I read this. All three layers converge on the same taste.\n\n```\n// biome.json\n{\n  \"formatter\": { \"indentStyle\": \"space\", \"indentWidth\": 2 },\n  \"linter\": {\n    \"rules\": {\n      \"recommended\": true,\n      \"suspicious\": { \"noExplicitAny\": \"error\" },\n      \"complexity\": { \"noForEach\": \"warn\" }\n    }\n  },\n  \"organizeImports\": { \"enabled\": true }\n}\n// package.json (excerpt)\n{\n  \"scripts\": {\n    \"check\": \"biome check .\",\n    \"fix\": \"biome check --apply .\",\n    \"test\": \"vitest run\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"lint-staged\": {\n    \"*.{ts,tsx,json}\": [\"biome check --apply\"]\n  }\n}\nnpx husky init\necho \"npx lint-staged\"                > .husky/pre-commit\necho \"npm run test && npm run type-check\" > .husky/pre-push\n```\n\nWhat this catches before a PR is even opened: any `any`\n\n, unformatted code, broken imports, failing tests, TypeScript errors. If it fails, the commit doesn't land. Nothing about this reaches Layer 2 or Layer 3.\n\nBiome vs. Prettier + ESLint: Biome is one binary and roughly 10× faster on this project's lint step. The Next.js 15 flat-config transition made ESLint plugins painful enough to justify the move; your mileage may vary if you have deep custom ESLint rules.\n\n```\n# .coderabbit.yaml\nlanguage: en\nreviews:\n  profile: default\n  auto_review:\n    enabled: true\n    drafts: false\n  path_instructions:\n    - path: \"src/app/**\"\n      instructions: \"Server Component is the default. Minimize 'use client'.\"\n    - path: \"src/repositories/**\"\n      instructions: \"Repository Pattern via Prisma. No raw SQL.\"\n    - path: \"src/services/**\"\n      instructions: \"Business logic. Return Result<T, E>, do not throw.\"\n    - path: \"**\"\n      instructions: |\n        Follow AGENTS.md Code Direction.\n        Use Conventional Comments — issue: for anti-patterns, praise: for pattern hits.\n  path_filters:\n    - \"!**/*.lock\"\n    - \"!**/node_modules/**\"\n    - \"!**/.next/**\"\n  request_changes_workflow:\n    enabled: true\nchat:\n  auto_reply: true\n```\n\nCodeRabbit reads AGENTS.md, catches the Next.js 15 specifics I forget — treating `params`\n\nas sync in a page component, non-serializable props crossing an RSC boundary, `use client`\n\ninside a component that could have stayed on the server. Copilot's PR review runs in parallel on architectural sanity and catches different classes of miss.\n\nRunning both is not redundant. They disagree often enough that the disagreement itself is signal — when both flag the same line, it's usually real. When only one flags it, I look but don't automatically act.\n\nThe `path_instructions`\n\nblock is what makes this layer stop being generic. Without those per-directory rules, CodeRabbit just gives you the same generic \"consider adding tests\" comment 40 times per PR.\n\nBy the time a PR opens for my eyes, three things are already true:\n\n`biome check`\n\n, `vitest`\n\n, and `tsc`\n\nare green (Layer 1)So the only things left are:\n\nThat's it. That's what 25 minutes covers.\n\n| Before | After | |\n|---|---|---|\n| Layer 1 comments I wrote by hand | ~15/PR | 0 |\n| Layer 2 pattern comments I wrote | ~8/PR | 1–2 (backing up the AI when it was right) |\n| Layer 3 design/business comments | ~5/PR | 4–5 (same as before — this is the work) |\nTotal time per PR |\n~4h |\n~25min |\n\nThe Layer 3 number barely moved. That's the point. The work that requires me is unchanged. What disappeared is the work that never required me in the first place.\n\nHonest failure modes I've hit:\n\n`merge`\n\nwithout reading. That's the trap. If nothing else, read the diff shape and confirm the The whole system is only worth it if Layer 3 stays honest.\n\nDo not add all three layers on the same day. That was my first mistake — dropping Biome, husky, CodeRabbit, and new AGENTS.md rules into the repo in one PR broke everyone's workflow for a week.\n\nOrder that worked:\n\n`AGENTS.md`\n\nonly. No enforcement. Team reads it, comments on it.`path_instructions`\n\nbased on what CodeRabbit gets wrong.Same reason you don't put 100kg on the bar on day one: the layers work because the team adapts to each one before the next lands.\n\nOne AI reviewer looking at your whole diff is doing every layer's job at once, which means it's doing none of them well. The trick is not a smarter model. The trick is that Layer 1 is deterministic, Layer 2 is opinionated, Layer 3 is judgment — and none of them are allowed to pretend to be the others.\n\nThe full playbook — including CodeRabbit rule tuning, Copilot integration, when to escalate to Claude Code review, and the parts I got wrong twice — is here:\n\n[Code Review Harness — the 3-layer setup, tuning guide, and the anti-patterns each layer misses](https://kenimoto.dev/books/harness-code-review?utm_source=devto&utm_medium=article&utm_campaign=nextjs-3-layer-review)", "url": "https://wpnews.pro/news/3-layers-of-code-review-on-next-js-typescript-i-cut-human-review-time-from-4h-to", "canonical_source": "https://dev.to/kenimo49/3-layers-of-code-review-on-nextjs-typescript-i-cut-human-review-time-from-4h-to-25min-per-pr-45i", "published_at": "2026-07-29 13:00:01+00:00", "updated_at": "2026-07-29 13:07:01.339931+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "artificial-intelligence"], "entities": ["Next.js", "TypeScript", "Prisma", "Biome", "CodeRabbit", "Copilot", "Husky"], "alternates": {"html": "https://wpnews.pro/news/3-layers-of-code-review-on-next-js-typescript-i-cut-human-review-time-from-4h-to", "markdown": "https://wpnews.pro/news/3-layers-of-code-review-on-next-js-typescript-i-cut-human-review-time-from-4h-to.md", "text": "https://wpnews.pro/news/3-layers-of-code-review-on-next-js-typescript-i-cut-human-review-time-from-4h-to.txt", "jsonld": "https://wpnews.pro/news/3-layers-of-code-review-on-next-js-typescript-i-cut-human-review-time-from-4h-to.jsonld"}}