page.tsx — The Route's UI

export default — That's a Page

page.tsx — The Route's UI

A page.tsx file exports the React component rendered at that route's URL. It can be async and receive params and searchParams.

4 min read Level 1/5 #nextjs#page#route
What you'll learn
  • Create `app/about/page.tsx` with a default export
  • Use async server components for data fetching
  • Receive `params` and `searchParams` (both are Promises in Next 15)

A page.tsx is the leaf of a route. Whatever you export default is what users see at that URL.

The Simplest Page

// app/about/page.tsx → /about
export default function AboutPage() {
  return <h1>About Us</h1>;
}

No imports, no router config. The file exists, so the route exists.

Pages Can Be Async

Server components — which is what pages are by default — can be async functions. That lets you fetch data inline, without useEffect or loaders.

// app/users/page.tsx
type User = { id: string; name: string };

export default async function UsersPage() {
  const users: User[] = await fetch('https://api.example.com/users', {
    next: { revalidate: 60 },
  }).then((r) => r.json());

  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

params and searchParams

Dynamic routes pass the captured params in. In Next.js 15 both params and searchParams are Promises — you await them before reading.

// app/blog/[slug]/page.tsx → /blog/hello?ref=twitter
type PageProps = {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ ref?: string }>;
};

export default async function PostPage({ params, searchParams }: PageProps) {
  const { slug } = await params;
  const { ref } = await searchParams;

  return (
    <article>
      <h1>{slug}</h1>
      {ref && <p>Referred by {ref}</p>}
    </article>
  );
}

That is the whole API for pages. Next we look at layouts, the shared wrappers that sit above them.

layout.tsx — Shared UI Across Segments →