Generate pages from data with dynamic segments, pre-build them with generateStaticParams, and tidy folders with route groups.
Why: you don’t hand-write a page per blog post. Wrap a folder name in square brackets, like [slug], and one file serves every value of that segment. Note: in this Next.js, params is a Promise — you must await it before reading the value.
// app/blog/[slug]/page.tsx -> /blog/anything
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <h1>Post: {slug}</h1>
}Why: [...slug] matches any number of segments (/docs/a/b/c), and [[...slug]] makes it optional so it also matches the bare folder (/docs). Useful for docs and file-tree style URLs.
// app/docs/[[...slug]]/page.tsx
// matches /docs, /docs/getting-started, /docs/api/use-router ...
export default async function DocsPage({
params,
}: {
params: Promise<{ slug?: string[] }>
}) {
const { slug = [] } = await params
return <p>Path segments: {slug.join(' / ') || '(home)'}</p>
}Why: export generateStaticParams to tell Next.js which dynamic pages to build ahead of time. Those pages become static HTML — instant to load and cheap to serve — instead of being rendered on every request.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog').then((r) => r.json())
// Return one object per page you want pre-built
return posts.map((post: { slug: string }) => ({ slug: post.slug }))
}
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <h1>{slug}</h1>
}Why: a folder in (parentheses) groups routes WITHOUT adding to the URL — handy for giving one set of pages its own layout. A folder starting with _underscore is private: it holds helpers and never becomes a route.
// app/(marketing)/page.tsx -> still "/", just grouped
// app/(marketing)/about/page.tsx -> "/about"
// app/(marketing)/layout.tsx -> layout only for this group
// app/blog/_components/PostCard.tsx
// the _components folder is NOT a route — safe place for UI helpers
export function PostCard() {
return <article>...</article>
}A page gets its data through its props. "params" is the dynamic part of the URL (the [slug] folder), and "searchParams" is the ?key=value part. PageProps is a ready-made type that Next.js builds for you from your folders, so you tell it the route once and TypeScript knows the shape of params and searchParams — no writing the type by hand, plus autocomplete and typo-catching for free. Good to know: PageProps is created when you run next dev or next build (or next typegen), so run the dev server once if TypeScript says it cannot find it. You do not import it — it is available everywhere.
// app/blog/[slug]/page.tsx?sort=new
export default async function Page(props: PageProps<'/blog/[slug]'>) {
// params = the [slug] part of the URL -> { slug: 'hello-world' }
const { slug } = await props.params
// searchParams = the ?key=value part -> { sort: 'new' }
const { sort } = await props.searchParams
return <h1>Blog post: {slug} (sorted by {sort})</h1>
}