Pages & File-based Routing
The big idea: folders are URLs
In the App Router you never write route tables. Every folder inside app/ is a URL segment, and a folder becomes a visitable page only when it contains a page.jsx file.
Create your first routes
// app/about/page.jsx
export default function AboutPage() {
return (
<main className="container">
<h1>About EduSite</h1>
<p>We teach coding in Hindi + English since 2018.</p>
</main>
)
}
// app/courses/page.jsx
export default function CoursesPage() {
return <h1>All courses</h1>
}
// app/courses/web-development/page.jsx
export default function WebDevPage() {
return <h1>Web Development course</h1>
}Save the files and open /about, /courses and /courses/web-development — no restart needed.
Nested routes
Put folders inside folders to build deeper URLs. app/courses/page.jsx is the index of /courses, and child folders create pages below it.
Folder → URL map
📁 app/ page.jsx # / 📁 about/ page.jsx # /about 📁 courses/ page.jsx # /courses 📁 web-development/ page.jsx # /courses/web-development 📁 blog/ page.jsx # /blog 📁 contact/ page.jsx # /contact ContactForm.jsx # NOT a route (no page.jsx name)
Colocation: keep helpers next to the page
Only page.jsx (and route.js for APIs) is public. You can safely keep components, CSS modules and helpers in the same folder. In EduSite, app/contact/ContactForm.jsx and app/contact/actions.js sit next to the contact page but are not URLs.
app/_utils/. You will meet this again in the Route Groups lesson.Reading the URL: searchParams
Query strings like ?level=beginner arrive in the page as the searchParams prop. In Next.js 16 it is a Promise, so the page must be async and await it:
// app/courses/page.jsx → /courses?level=beginner&sort=fee
export default async function CoursesPage({ searchParams }) {
const { level = 'all', sort = 'popular' } = await searchParams
return (
<main>
<h1>Courses</h1>
<p>Showing level: {level} · sorted by: {sort}</p>
</main>
)
}searchParams makes the page render on every request (dynamic), because the result depends on the URL.Pages are Server Components
By default a page runs on the server. It can be async and read files, environment variables or a database directly — that code never reaches the visitor's browser.
// app/blog/page.jsx — a page can be async and read files or a database directly
import { getAllPosts } from '@/lib/posts'
export default async function BlogPage() {
const posts = getAllPosts() // runs on the server, never sent to the browser
return (
<ul>
{posts.map((p) => <li key={p.slug}>{p.title}</li>)}
</ul>
)
}Common mistakes
app/about.jsx→ not a route. Useapp/about/page.jsx.- Spaces or capitals in folder names (
About Us) → ugly URLs like/About%20Us. - Reading
searchParams.levelwithoutawait→ it is a Promise, so the value isundefined. - Two folders producing the same URL (for example via route groups) → build error "two parallel pages".
Summary
- Folders in
app/become URL segments;page.jsxmakes them public. - Nest folders for deeper URLs; keep helper files next to pages.
searchParams(a Promise) gives you query-string values.- Pages are Server Components and can be
async.
Create /courses/python-ai and /courses/android-kotlin pages. On /courses, read ?level= and show "Filter: beginner" when it is present.
