Async Server Components
Make a Server Component async and await your data right inside it β no useEffect or loading state needed.
Syntax
export default async function Page() { const data = await fetch(...); }Because Server Components run on the server, they can be async and await data directly in the function body. There is no useEffect, no client-side loading flag, and no extra API round trip β the data is fetched during rendering and the finished HTML is sent to the browser.
Why this is nice
- Less code: no effect, no state, no fetch-on-mount.
- Faster: data is ready before the HTML reaches the user.
- Secure: fetch logic and secrets stay on the server.
Example
// app/users/page.tsx β async Server Component
export default async function Users() {
const res = await fetch('https://api.example.com/users');
const users: { id: number; name: string }[] = await res.json();
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}When to use it
- A news page awaits a CMS API inside the component function, getting data without any client-side effect.
- A profile page awaits multiple database calls in parallel using Promise.all inside an async Server Component.
- A pricing page fetches live plan data in the component so it is always fresh without exposing the API key to the browser.
More examples
Awaiting fetch in a page
Marking the component async lets you await any Promise directly without useEffect or useState.
// app/news/page.tsx
export default async function NewsPage() {
const res = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json');
const ids: number[] = await res.json();
return <p>Top story ID: {ids[0]}</p>;
}Parallel data fetching
Promise.all fires both requests simultaneously, halving the wait time compared to sequential awaits.
// app/profile/[id]/page.tsx
import { getUser, getOrders } from '@/lib/db';
export default async function ProfilePage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);
return (
<div>
<h1>{user.name}</h1>
<p>{orders.length} orders</p>
</div>
);
}Async layout with session
Layouts can also be async, allowing session checks that apply to every page in a segment.
// app/app/layout.tsx
import { getSession } from '@/lib/auth';
export default async function AppLayout({ children }: { children: React.ReactNode }) {
const session = await getSession();
return (
<div>
<p>Signed in as: {session.email}</p>
{children}
</div>
);
}
Discussion