How to Fix 'Found Page Without a React Component as Default Export' in Next.js
Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 2–10 mins
TL;DR
- What broke: A file inside
pages/is missing a valid React component as itsdefaultexport — Next.js cannot map it to a route, sonext buildhard-fails. - How to fix it: Ensure every file in
pages/(except_app.tsx,_document.tsx, and API routes underpages/api/) exports a React functional or class component asdefault. - Fast path: Use our Client-Side Sandbox above to paste your offending page file and auto-refactor it — it detects the missing/invalid export and rewrites it instantly.
The Incident (What Does the Error Mean?)
During next build, the framework statically analyzes every file in your pages/ directory. It expects each file's default export to be a renderable React component. When it finds anything else — or nothing — it throws:
Build optimization failed: found page without a React Component as default export in pages directory
pages/dashboard.tsx
Immediate consequence: The build exits with a non-zero code. Your CI/CD pipeline fails. Nothing ships. If this slips through a misconfigured pipeline that deploys on partial success, you get a broken route that 500s in production.
Common triggers:
| Scenario | Example |
|---|---|
| File exports a plain object | export default { title: 'page' } |
| File exports a string or primitive | export default 'hello' |
| File has only named exports, no default | export const Page = () => <div/> |
File is a utility/helper accidentally placed in pages/ |
A constants file, a type-only file |
| Async function not wrapped as a component | export default async function getData() |
| Empty file committed by accident | — |
The Blast Radius
This is not a runtime warning — it is a compile-time hard block. The blast radius depends on your pipeline:
- Monorepo with shared
pages/symlinks: One bad file blocks builds for every app consuming that directory. - Incremental Static Regeneration (ISR) at scale: If this surfaces during a revalidation build triggered by on-demand ISR, your entire revalidation queue stalls.
- Preview deployments (Vercel/Netlify): Every PR gets a failed deploy, breaking the review workflow for the whole team.
- No pre-commit hooks: Developers only discover this on push, wasting CI minutes and blocking the merge queue.
There is no graceful degradation here. Next.js will not skip the bad file and build the rest. It stops.
How to Fix It
Basic Fix
The offending file must export a React component as default. Minimum viable fix:
- export default { title: 'Dashboard' }
+ import React from 'react'
+
+ export default function DashboardPage() {
+ return <main><h1>Dashboard</h1></main>
+ }
- export const ProfilePage = () => <div>Profile</div>
+ const ProfilePage = () => <div>Profile</div>
+
+ export default ProfilePage
- export default async function fetchData() {
- return await fetch('/api/data')
- }
+ // Move data-fetching logic to getServerSideProps or a lib/ utility
+ export default function DataPage() {
+ return <div>Data</div>
+ }
+
+ export async function getServerSideProps() {
+ const res = await fetch('/api/data')
+ const data = await res.json()
+ return { props: { data } }
+ }
Enterprise Best Practice
Rule 1: Nothing non-component lives in pages/. Move all utilities, constants, types, and helpers to lib/, utils/, or src/ directories. Enforce this with path aliasing in tsconfig.json.
Rule 2: Use a TypeScript page type contract. Force every page file to satisfy the NextPage type, which will surface missing default exports at compile time before next build even runs:
- const Dashboard = () => <div>Dashboard</div>
- export { Dashboard }
+ import type { NextPage } from 'next'
+
+ const Dashboard: NextPage = () => <div>Dashboard</div>
+
+ export default Dashboard
Rule 3: ESLint enforcement. The @next/next ESLint plugin includes rules that catch this before commit:
- // .eslintrc.json
- { "extends": ["next"] }
+ // .eslintrc.json
+ {
+ "extends": ["next/core-web-vitals"],
+ "rules": {
+ "@next/next/no-page-custom-font": "error"
+ }
+ }
Run next lint as a pre-build step — it will flag invalid page exports before next build wastes your CI minutes.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Pre-commit: lint-staged + next lint
# .husky/pre-commit
npx lint-staged
# lint-staged.config.js
module.exports = {
'pages/**/*.{ts,tsx,js,jsx}': ['next lint --fix', 'git add'],
}
2. CI pipeline gate — fail fast before next build
# .github/workflows/ci.yml
jobs:
lint-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- name: Lint (catches invalid page exports)
run: npx next lint
- name: Build
run: npx next build
By running next lint as a separate, earlier step, you get a fast failure with a precise file reference instead of the cryptic build optimizer error.
3. Custom ESLint rule for default export enforcement
If you want zero tolerance across all pages:
// eslint-plugin-local/rules/require-page-default-export.js
module.exports = {
create(context) {
const filename = context.getFilename()
if (!filename.includes('/pages/')) return {}
if (filename.includes('/api/')) return {}
let hasDefaultExport = false
return {
ExportDefaultDeclaration() { hasDefaultExport = true },
'Program:exit'() {
if (!hasDefaultExport) {
context.report({ loc: { line: 1, column: 0 }, message: 'Page files must have a default React component export.' })
}
},
}
},
}
4. Checkov / Semgrep for IaC-adjacent Next.js config checks
If you manage Next.js deployments via Terraform or CDK, add a Semgrep rule to your pipeline that scans the pages/ directory for files lacking default exports during the plan/preview stage — catching it before infrastructure provisioning even starts.
Bottom line: This error is 100% preventable with next lint in CI. Every minute your build sits broken on this is a minute you didn't have a lint gate.