Next.js page/layout convention exports are in correct file types
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.
// 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/.tsxfiles undersrc/,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)metadataandgenerateMetadata— valid ONLY in Server Componentpage.tsxorlayout.tsxfiles (files underapp/namedpage.*orlayout.*that do NOT have'use client'as the first directive). If apage.tsxorlayout.tsxhas'use client'at the top and exportsmetadataorgenerateMetadata, flag it — Next.js silently ignores metadata exports from Client Components. (b)generateStaticParams— valid ONLY inpage.tsxorlayout.tsxwith a[param]dynamic segment in its path. (c)dynamic,revalidate,fetchCache,runtime,preferredRegion,maxDuration— valid inpage.tsx,layout.tsx, androute.tsfiles (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., incomponents/,hooks/,utils/,lib/). Do NOT flag internal variables namedmetadatathat are not exported — only flagexport const metadata/export function generateMetadatapatterns. 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.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 exportsmetadataorgenerateMetadata— 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:
// 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
dynamicorrevalidatefound in component files, move them to the page/layout/route file that imports the component.
External references
- cwe · CWE-489 — Active Debug Code: 'use client' pages with metadata are a silent misconfiguration analogous to debug/placeholder code left active
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ai-slop-hallucinations·automated
- 2026-04-20·v1.1.0·Add Phase 6.1 detect-node snippet for 'use client' + route-convention misplacement·by cakleinman