DEV Community

Cover image for 3 Layers of Code Review on Next.js + TypeScript: I Cut Human Review Time From 4h to 25min per PR
Ken Imoto
Ken Imoto

Posted on • Originally published at kenimoto.dev

3 Layers of Code Review on Next.js + TypeScript: I Cut Human Review Time From 4h to 25min per PR

The number I was chasing

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" back-and-forth. New Repository class? Ninety minutes because I was reading through 12 similar files to double-check the pattern.

I 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". Only 30% touched actual design decisions.

So 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.

The split

Layer 1: Biome + husky (pre-commit, pre-push) — mechanical
Layer 2: CodeRabbit + Copilot on PR open       — pattern-level
Layer 3: Me                                    — design + business rule
Enter fullscreen mode Exit fullscreen mode

Each 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.

That's the whole idea. The rest is config files.

Layer 0: AGENTS.md — the thing every layer reads

Before any config: the file all three layers point to.

# AGENTS.md

## Project

Next.js 15 + TypeScript + Prisma + PostgreSQL

## Architecture

- App Router (Server Component default)
- Repository Pattern (data access via Prisma)
- Service layer (business logic)
- Result<T, E> for error handling — do not throw

## Code Direction

Increase:
- Repository Pattern → src/repositories/*-repository.ts
- Result<T, E> → src/types/result.ts
- Data fetching inside Server Components
- Prisma.select for narrow column lists

Decrease:
- any (use unknown)
- Business logic in Route Handlers
- throw for control flow
- Client Component fetching (unless SWR/React Query)
- Files over 300 lines

## Review Policy

- Conventional Comments syntax
- Approval bar: codebase health improves = approve
- First-response SLA: 24h
Enter fullscreen mode Exit fullscreen mode

CodeRabbit reads this. Copilot reads this. I read this. All three layers converge on the same taste.

Layer 1: Biome + husky (mechanical)

// biome.json
{
  "formatter": { "indentStyle": "space", "indentWidth": 2 },
  "linter": {
    "rules": {
      "recommended": true,
      "suspicious": { "noExplicitAny": "error" },
      "complexity": { "noForEach": "warn" }
    }
  },
  "organizeImports": { "enabled": true }
}
Enter fullscreen mode Exit fullscreen mode
// package.json (excerpt)
{
  "scripts": {
    "check": "biome check .",
    "fix": "biome check --apply .",
    "test": "vitest run",
    "type-check": "tsc --noEmit"
  },
  "lint-staged": {
    "*.{ts,tsx,json}": ["biome check --apply"]
  }
}
Enter fullscreen mode Exit fullscreen mode
npx husky init
echo "npx lint-staged"                > .husky/pre-commit
echo "npm run test && npm run type-check" > .husky/pre-push
Enter fullscreen mode Exit fullscreen mode

What this catches before a PR is even opened: any any, 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.

Biome 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.

Layer 2: CodeRabbit + Copilot (pattern)

# .coderabbit.yaml
language: en
reviews:
  profile: default
  auto_review:
    enabled: true
    drafts: false
  path_instructions:
    - path: "src/app/**"
      instructions: "Server Component is the default. Minimize 'use client'."
    - path: "src/repositories/**"
      instructions: "Repository Pattern via Prisma. No raw SQL."
    - path: "src/services/**"
      instructions: "Business logic. Return Result<T, E>, do not throw."
    - path: "**"
      instructions: |
        Follow AGENTS.md Code Direction.
        Use Conventional Comments — issue: for anti-patterns, praise: for pattern hits.
  path_filters:
    - "!**/*.lock"
    - "!**/node_modules/**"
    - "!**/.next/**"
  request_changes_workflow:
    enabled: true
chat:
  auto_reply: true
Enter fullscreen mode Exit fullscreen mode

CodeRabbit reads AGENTS.md, catches the Next.js 15 specifics I forget — treating params as sync in a page component, non-serializable props crossing an RSC boundary, use client inside 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.

Running 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.

The path_instructions block 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.

Layer 3: me (design + business rule)

By the time a PR opens for my eyes, three things are already true:

  • biome check, vitest, and tsc are green (Layer 1)
  • CodeRabbit and Copilot have already commented on pattern violations (Layer 2)

So the only things left are:

  1. Design — does this implementation match Architecture in AGENTS.md?
  2. Business logic — does it actually do what the spec says?
  3. Direction drift — is there a new pattern here worth adding to AGENTS.md, or an anti-pattern worth naming?

That's it. That's what 25 minutes covers.

The number I actually got

Before After
Layer 1 comments I wrote by hand ~15/PR 0
Layer 2 pattern comments I wrote ~8/PR 1–2 (backing up the AI when it was right)
Layer 3 design/business comments ~5/PR 4–5 (same as before — this is the work)
Total time per PR ~4h ~25min

The 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.

Where each layer breaks

Honest failure modes I've hit:

  • Layer 1 false confidence: a green Biome + green tests can still land a broken feature. Layer 1 is about hygiene, not correctness.
  • Layer 2 hallucination: CodeRabbit sometimes proposes changes that violate AGENTS.md. When it does, I don't argue in the PR — I add the missing rule to AGENTS.md so the next PR is quieter.
  • Layer 3 skipping itself: when Layers 1 and 2 both approve, there's a real temptation to merge without reading. That's the trap. If nothing else, read the diff shape and confirm the intent matches the description.

The whole system is only worth it if Layer 3 stays honest.

Roll it in gradually, not all at once

Do 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.

Order that worked:

  1. Week 1: AGENTS.md only. No enforcement. Team reads it, comments on it.
  2. Week 2: Biome + husky on pre-commit. Format-only.
  3. Week 3: Biome lint errors + pre-push tests.
  4. Week 4: CodeRabbit on new PRs.
  5. Week 5+: Refine path_instructions based 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.

The takeaway

One 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.


The full playbook — including CodeRabbit rule tuning, Copilot integration, when to escalate to Claude Code review, and the parts I got wrong twice — is here:

Code Review Harness — the 3-layer setup, tuning guide, and the anti-patterns each layer misses

Top comments (0)