Docs / Rules

Rules

Architect ships one stack blueprint: nextjs-app-router. It carries eleven anti-patterns. Ten are checked deterministically; one is guidance for a coding agent to read.

Run architect check --list-rules to print the current list straight from the blueprint — that output is generated from the source, so it cannot drift from this page.

The eleven checked rules

RuleSeverityFires when
direct_db_in_pagecriticalA database client is imported in page.tsx or layout.tsx
direct_db_in_routecriticalA database client is imported in route.ts
leaked_server_secretcriticalA 'use client' file reads a non-NEXT_PUBLIC_ env var
illegal_importcriticalcomponents/ imports from app/
use_client_everywherewarning'use client' sits on a layout
client_data_fetching_by_defaultwarningA client component fetches in useEffect
server_action_throwswarningA Server Action throws instead of returning a result
scattered_process_envwarningprocess.env is read outside lib/config.ts
alert_for_errorswarningalert() is used to show an error
oversized_extractionwarningA file exceeds 300 LOC
missing_layerwarningA required directory from the blueprint does not exist

missing_layer is derived from the blueprint's structure.required_dirs rather than from a detect: block.

Agent-only

RuleWhy it is not checked
auth_mechanism_mismatchRequires cross-file semantic judgement — the login flow issuing one token type while an API route validates another. A static matcher would produce false positives, so it stays as guidance in the blueprint.

The detect: schema

Rules are data, not code. An anti-pattern becomes checkable by gaining a detect: block:

- id: direct_db_in_page
  severity: critical
  detect:
    kind: import
    paths: ["app/**/page.tsx", "app/**/layout.tsx"]
    modules: ["@prisma/client", drizzle-orm, mongoose]
    message: "Database client imported directly in a page or layout component."
    fix: "Move the query into lib/ and call that function from the page."
  description: "..."     # read by the coding agent, never by check
  bad_example: |  ...
  good_example: |  ...

An anti-pattern without detect: is agent-only guidance and is never reported.

Matcher kinds

kindFires whenKey fields
importA module is imported inside paths — or, with bindings, one hop away through a workspace package or local filemodules, bindings
import_directionA file under from imports one under tofrom, to
directiveA file carries a directive prologuevalue
callA named function is calledcallee
memberA member expression appearsobject, property
throwA throw statement appears
metricA file metric exceeds a ceilingmetric, gt

Shared fields

FieldEffect
pathsGlobs the rule applies to. Omitted means every file.
not_pathsGlobs excluded, applied after paths
requires_directiveThe file must also carry this directive, e.g. use client
requires_callThe file must also contain this call, e.g. useEffect
inside_callback_ofcall only. The call must sit lexically inside a function passed to this call — useEffect(() => { fetch() }) yes, a fetch in an onClick handler no
method_notcall only. Skip a fetch whose literal method: is one of these, e.g. [POST, PUT, PATCH, DELETE]
not_matchingMatched text starting with any of these is not a violation
bindingsimport only. Names that, when imported from a module that depends on or imports a modules entry, count as importing that module. prisma yes, listUsers no. Type-only imports never match.
messageWhat is wrong. Shown to the user.
fixWhat to do about it. States the move, not the diagnosis.

Path matching

Globs are anchored at a segment boundary, not the repository root, because projects put the App Router in either app/ or src/app/:

PatternPathMatch
app/**/page.tsxapp/users/page.tsx
app/**/page.tsxsrc/app/users/page.tsx
app/**/page.tsxsrc/app/(dashboard)/team/page.tsx
app/**/page.tsxsrc/myapp/users/page.tsx
app/*.tsxsrc/app/users/page.tsx❌ (one segment only)

Matching is AST-based

Every matcher runs against a real parse, not a text scan. A commented-out alert() and a string that merely mentions process.env are not violations.

Two violations never share a file:line — where rules legitimately overlap, only the most severe is reported.

Adding a rule

See Contributing. Every rule needs a violation fixture and a clean fixture proving it stays silent on correct code.

Help us improve this page