# Next.js page/layout convention exports are in correct file types

- **Pattern:** `ab-000033` (`ai-slop-hallucinations.route-references.page-conventions-in-correct-files`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-20
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000033
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000033)

## Why it matters

A `metadata` export inside a `'use client'` page is one of the most common silent failures in AI-generated Next.js code: the model writes both directives in the same file, Next.js drops the metadata without any warning, and the page ships with no `<title>` or `<meta description>`. The same applies to `generateStaticParams` placed in a component file — it runs nowhere. CWE-489 (Active Debug Code) maps loosely, but the practical harm is SEO blackout and broken static generation — neither of which surfaces in development because the app still renders.

## Severity rationale

Medium because the failure is silent data loss (missing metadata, skipped static params) rather than a security vulnerability, but it directly harms SEO and deployment correctness.

## Remediation

Split `'use client'` pages that export `metadata` into a server wrapper and a client component. Move all convention exports to the server file.

```tsx
// app/dashboard/page.tsx — server component (no 'use client')
export const metadata = { title: 'Dashboard', description: 'Your overview' }
export { default } from './dashboard-client'

// app/dashboard/dashboard-client.tsx
'use client'
import { useState } from 'react'
export default function Dashboard() {
  const [count, setCount] = useState(0)
  return <div>{count}</div>
}
```

For `dynamic` or `revalidate` found in component files under `src/components/`, move the export to the `page.tsx`, `layout.tsx`, or `route.ts` that imports the component. Config exports have no effect in component files.

## Detection

- **ID:** `page-conventions-in-correct-files`
- **Severity:** `medium`
- **What to look for:** When the framework is Next.js, scan all `.ts`/`.tsx` files under `src/`, `app/`, `lib/`, `components/`, `hooks/`, `utils/`. For each file, extract every named export (`export const X`, `export function X`, `export async function X`). Check each export name against this list of Next.js convention names that are ONLY meaningful in specific file types: `metadata`, `generateMetadata`, `generateStaticParams`, `dynamic`, `revalidate`, `fetchCache`, `runtime`, `preferredRegion`, `maxDuration`. For each convention export found, verify it is in a correct file location: (a) `metadata` and `generateMetadata` — valid ONLY in Server Component `page.tsx` or `layout.tsx` files (files under `app/` named `page.*` or `layout.*` that do NOT have `'use client'` as the first directive). If a `page.tsx` or `layout.tsx` has `'use client'` at the top and exports `metadata` or `generateMetadata`, flag it — Next.js silently ignores metadata exports from Client Components. (b) `generateStaticParams` — valid ONLY in `page.tsx` or `layout.tsx` with a `[param]` dynamic segment in its path. (c) `dynamic`, `revalidate`, `fetchCache`, `runtime`, `preferredRegion`, `maxDuration` — valid in `page.tsx`, `layout.tsx`, and `route.ts` files (these work in both Server and Client Components). Flag any convention export found in files that are NOT page/layout/route convention files (e.g., in `components/`, `hooks/`, `utils/`, `lib/`). Do NOT flag internal variables named `metadata` that are not exported — only flag `export const metadata` / `export function generateMetadata` patterns. Count all convention exports inspected, total in correct locations, total misplaced.
- **Detector snippet (Node-capable tools only):** If the tool has shell + Node, scan every source file for the `'use client'` directive and, in any file that has it, look for misplaced Next.js route-convention exports (metadata, generateMetadata, dynamic, revalidate, generateStaticParams, etc.). Output reports misplaced count. If exit 0 with "misplaced=0", report pass. If "misplaced>0", report fail. Exit >=2 or command not found — fall back to prose reasoning below.

  ```detect-node
  node -e 'const fs=require("fs"),path=require("path");const EX=["metadata","generateMetadata","dynamic","revalidate","generateStaticParams","dynamicParams","fetchCache","runtime"];function w(d,o=[]){try{for(const e of fs.readdirSync(d,{withFileTypes:true})){if(e.name==="node_modules"||e.name===".next")continue;const p=path.join(d,e.name);e.isDirectory()?w(p,o):o.push(p)}}catch{}return o}let bad=[];for(const f of w("src")){if(!/\.(ts|tsx|js|jsx)$/.test(f))continue;const s=fs.readFileSync(f,"utf-8");if(!/[\u0022\u0027]use client[\u0022\u0027]/.test(s))continue;for(const ex of EX){const re=new RegExp("export\\s+(const|async\\s+function|function)\\s+"+ex+"\\b");if(re.test(s)){bad.push(f+": "+ex);break}}}console.log("misplaced="+bad.length);bad.slice(0,5).forEach(x=>console.log(x))'
  ```

- **Pass criteria:** 0 convention exports are in incorrect file locations. Report: "X convention exports inspected, Y in correct locations, 0 misplaced."
- **Fail criteria:** At least 1 convention export is in a file where Next.js silently ignores it.
- **Do NOT pass when:** A `'use client'` page or layout exports `metadata` or `generateMetadata` — this is a common AI error where the model adds metadata to a Client Component, and Next.js silently drops it without any build warning.
- **Skip (N/A) when:** Framework is not Next.js OR no convention exports found in any source file.
- **Detail on fail:** `"2 misplaced convention exports: 'export const metadata' in src/app/dashboard/page.tsx (page has 'use client' — metadata is silently ignored in Client Components), 'export const dynamic' in src/components/DataTable.tsx (only valid in page/layout/route files)."`
- **Remediation:** Next.js convention exports are silently ignored when placed in the wrong file type — no build error, no runtime warning, just missing behavior. Fix each one:

  ```tsx
  // Bad: metadata in a 'use client' page is silently ignored
  'use client'
  export const metadata = { title: 'Dashboard' }  // ← ignored!

  // Good: split into a server wrapper
  // app/dashboard/page.tsx (server component)
  export const metadata = { title: 'Dashboard' }
  export { default } from './dashboard-client'

  // app/dashboard/dashboard-client.tsx
  'use client'
  export default function Dashboard() { /* ... */ }
  ```

  For config exports like `dynamic` or `revalidate` found in component files, move them to the page/layout/route file that imports the component.

---

## External references

- cwe CWE-489

Taxons: code-quality

HTML version: https://auditbuffet.com/patterns/ab-000033
