nextjs-static-rules
v0.1.6
Published
Deterministic static rules for Next.js 13–16 — catches App Router misuse, async params bugs, server/client boundary violations, and more.
Downloads
29
Maintainers
Readme
nextjs-static-rules
Deterministic static rules for Next.js 13–16. Catches App Router misuse, async params bugs, server/client boundary violations, missing cache options, and more — from a git diff, in milliseconds.
No LLM. No config. Zero false-positive rate on the patterns it covers.
Why
Next.js 15 introduced async params/searchParams, a changed fetch() default, and stricter server/client boundaries. These changes are silent — your build passes, your types check out, and then you get a runtime bug or stale cache in production.
This library encodes 20+ rules that catch these issues in your CI before they merge.
Install
npm install --save-dev nextjs-static-rules
# or
pnpm add -D nextjs-static-rulesCLI
Run against your current git diff:
npx nextjs-static-rulesCheck only staged changes:
npx nextjs-static-rules --stagedOutput as JSON (great for CI scripts):
npx nextjs-static-rules --jsonExample output:
src/app/dashboard/page.tsx
✖ line 3 error [hooks/in-server-component]
`useState` cannot be used in a Server Component — hooks require a client runtime and will cause a build error.
✖ line 12 error [async-params/sync-params]
`params` must be `await`ed before destructuring in Next.js 15+
1 error(s), 0 warning(s)Exit code is 1 when any errors are found — plug it straight into CI.
Programmatic API
import { checkFile, classifyFile, runNextJsRules } from "nextjs-static-rules";
// Convenience: classify + run all rules in one call
const violations = checkFile("src/app/dashboard/page.tsx", patch);
// Or step by step
const fileType = classifyFile("src/app/dashboard/page.tsx", patch);
const violations = runNextJsRules("src/app/dashboard/page.tsx", fileType, patch);
for (const v of violations) {
console.log(v.line, v.severity, v.rule, v.body);
}patch is a standard unified diff string (what git diff produces). Each violation has:
| Field | Type | Description |
|---|---|---|
| line | number | Line number in the new file |
| severity | "error" \| "warning" | Errors fail CI; warnings are advisory |
| rule | string | Rule ID (e.g. hooks/in-server-component) |
| body | string | Explanation + diff suggestion (Markdown) |
Rules
Server / Client boundary
| Rule | Severity | What it catches |
|---|---|---|
| use-server/in-page-or-layout | error | "use server" at top of page/layout — makes the page unreachable |
| use-client/after-imports | error | "use client" placed after imports — silently ignored by Next.js |
| use-client/in-route-handler | warning | "use client" in a Route Handler — has no effect |
| hooks/in-server-component | error | React hooks (useState, useEffect, etc.) in a Server Component |
| server-actions/inline-in-client | error | Inline "use server" inside a Client Component file |
| server-actions/no-revalidation | warning | Server Action mutates data without calling revalidatePath/revalidateTag |
Async params (Next.js 15+)
| Rule | Severity | What it catches |
|---|---|---|
| async-params/sync-params | error | params accessed without await |
| async-params/sync-search-params | error | searchParams accessed without await |
| async-params/sync-cookies | error | cookies() or headers() called without await |
Metadata
| Rule | Severity | What it catches |
|---|---|---|
| metadata/in-client-component | warning | export const metadata in a Client Component — silently ignored |
| metadata/viewport-in-metadata | warning | viewport key inside metadata — deprecated since Next.js 14 |
Image component
| Rule | Severity | What it catches |
|---|---|---|
| image/missing-alt | error | <Image> without alt attribute |
| image/missing-dimensions | error | <Image> without width/height or fill |
| image/raw-img-tag | warning | Raw <img> in App Router — loses optimisation |
Middleware
| Rule | Severity | What it catches |
|---|---|---|
| middleware/broad-matcher | error | Matcher without _next exclusion — runs on static assets |
| middleware/missing-matcher | warning | No export const config — middleware runs on every request |
Caching
| Rule | Severity | What it catches |
|---|---|---|
| cache/fetch-no-options | warning | fetch() without explicit cache or revalidate option |
Security
| Rule | Severity | What it catches |
|---|---|---|
| secrets/next-public | error | NEXT_PUBLIC_SECRET / NEXT_PUBLIC_KEY etc. — secrets exposed to browser |
Pages Router in App Router
| Rule | Severity | What it catches |
|---|---|---|
| pages-router/wrong-router-import | error | import { useRouter } from 'next/router' in app/ |
| pages-router/head-import | error | import Head from 'next/head' in app/ |
| pages-router/data-fetching-exports | error | getServerSideProps / getStaticProps in app/ |
Navigation
| Rule | Severity | What it catches |
|---|---|---|
| navigation/raw-anchor-tag | warning | <a href="..."> for internal routes — use <Link> |
GitHub Actions
- name: Check Next.js rules
run: npx nextjs-static-rules --stagedOr on PRs specifically:
on: [pull_request]
jobs:
nextjs-rules:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: npx nextjs-static-rulesWant AI-powered reviews?
This package catches deterministic violations. For deeper analysis — architectural issues, logic bugs, security vulnerabilities, and context-aware suggestions — check out MergeWell: an AI PR reviewer built specifically for Next.js that uses these same rules plus Claude AI for inline GitHub comments.
License
MIT
