Async Components — Writing Server Components with async/await
In this tutorial, you will learn about Async Components. We cover key concepts, practical examples, and best practices to help you master this topic.
Async components are Server Components defined as async functions that use await for data fetching, allowing you to write data-fetching logic directly in the component body.
What You'll Learn
You will understand how async components work, how to handle multiple promises, how to structure large components, and how to combine async Server Components with Suspense for streaming.
Why It Matters
Async components eliminate the need for separate data fetching layers like getServerSideProps or getStaticProps. The component declares its data dependencies and React handles the execution.
Real-World Use
DodaZIP's analytics dashboard uses async components to query multiple database tables simultaneously, rendering user statistics, file upload counts, and system health metrics in a single component.
flowchart LR
A[Page Request] --> B[Async Server Component]
B --> C[await db.query 1]
B --> D[await db.query 2]
B --> E[await db.query 3]
C --> F[Rendered JSX]
D --> F
E --> F
F --> G[HTML to Client]
style B fill:#1e293b,color:#fff
style F fill:#0f172a,color:#fff
Defining an Async Component
An async component is simply a React component declared with the async keyword. It returns JSX after all awaited promises resolve.
async function RecentOrders() {
const orders = await db.orders.findRecent(10);
return (
<div>
<h2>Recent Orders</h2>
{orders.length === 0 ? (
<p>No orders yet.</p>
) : (
<ul>
{orders.map(order => (
<li key={order.id}>
Order #{order.id} — ${order.total} — {order.status}
</li>
))}
</ul>
)}
</div>
);
}
Expected output: A list of the ten most recent orders with their ID, total, and status. If no orders exist, a fallback message displays.
Composing Async Components
Async components can import and render other components, including both Server and Client Components.
async function UserProfilePage({ params }) {
const user = await db.users.findById(params.id);
const stats = await db.stats.getUserStats(params.id);
return (
<div>
<UserHeader user={user} />
<UserStats stats={stats} />
<UserActivityFeed userId={params.id} />
</div>
);
}
Expected output: The page fetches user data and stats in sequence, then renders three child components with the fetched data. Each child can be a Server or Client Component.
Using Suspense with Async Components
Wrap async components in Suspense boundaries to show fallback UI while data is loading. This enables streaming.
import { Suspense } from 'react';
async function SlowDataComponent() {
const data = await db.query('SELECT * FROM heavy_query');
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
async function FastDataComponent() {
const data = await db.query('SELECT * FROM light_query');
return <DataTable data={data} />;
}
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading quick stats...</p>}>
<FastDataComponent />
</Suspense>
<Suspense fallback={<p>Running heavy analysis...</p>}>
<SlowDataComponent />
</Suspense>
</div>
);
}
Expected output: The heading renders immediately. FastDataComponent appears first with a brief loading state. SlowDataComponent streams in later. The user sees content progressively.
Combining Server and Client Data Flow
Async components can pass fetched data to Client Components for interactive rendering.
'use client';
function InteractiveChart({ data }) {
const [filter, setFilter] = useState('all');
const filteredData = data.filter(d => filter === 'all' || d.category === filter);
return (
<div>
<select onChange={e => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="sales">Sales</option>
<option value="traffic">Traffic</option>
</select>
<pre>{JSON.stringify(filteredData, null, 2)}</pre>
</div>
);
}
// Server Component
async function AnalyticsPage() {
const data = await db.analytics.getFullData();
return <InteractiveChart data={data} />;
}
Expected output: The AnalyticsPage fetches data on the server and passes it to InteractiveChart as a prop. The client-side chart renders with a dropdown filter that works without additional server requests.
Common Mistakes
Forgetting that async components are Server Components only: You cannot use async in Client Components. If you need async data in a Client Component, use useEffect or a data fetching library.
Blocking the entire page with sequential awaits: Use Promise.all for independent data fetches. Sequential awaits create waterfalls that delay rendering.
Not handling the empty state: Always check if data exists before rendering. An empty array or null value can crash the render if you access nested properties.
Using async in children when parent handles fetching: Fetch data at the highest level needed and pass it down. Avoid fetching the same data in multiple components.
Mixing async and sync operations incorrectly: All data dependencies should be awaited before the component returns JSX. Do not try to fetch data conditionally after render.
Practice Questions
- How do you declare an async component in React?
By adding the async keyword before the function declaration and using await inside the function body.
- Can you use async in a Client Component?
No. Async functions as components are only supported in Server Components. Client Components must use hooks or data fetching libraries for async operations.
- What is the benefit of wrapping async components in Suspense?
Suspense allows the page to stream content progressively. Each Suspense boundary resolves independently, showing content as it becomes ready.
- How do you handle loading states without Suspense?
You cannot. Without Suspense, the page blocks until the async component resolves. Always wrap async components in Suspense for better UX.
- What happens if an async component throws an error?
The error propagates to the nearest error boundary. Create an error.js file in Next.js to catch and display errors gracefully.
Challenge
Build a page with three async components fetching data of different speeds (fast, medium, slow). Use Suspense boundaries to stream each section independently as it loads.
Frequently Asked Questions
Mini Project
Create a news aggregator page with three async components fetching from different API endpoints (headlines, sports, technology), each wrapped in a Suspense boundary with a custom loading skeleton.
What's Next
Learn about Streaming and Suspense to understand how React sends content progressively to the browser.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro