Skip to content

Async Components — Writing Server Components with async/await

DodaTech Updated 2026-06-28 5 min read

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

  1. 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.

  2. Blocking the entire page with sequential awaits: Use Promise.all for independent data fetches. Sequential awaits create waterfalls that delay rendering.

  3. 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.

  4. 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.

  5. 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

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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

Can I use top-level await in Server Components?

Top-level await is not supported in React components. Use await inside the async component function body instead.

Do async components affect SEO?

No. Async components render on the server and send complete HTML to the client. Search engines see the fully rendered content.

Can I pass async components as props?

No. You cannot pass an async component as a prop directly. Render it in place or wrap it in Suspense at the call site.

How do I test async components?

Use React testing libraries with async utilities. Render the component and wait for the data to resolve before asserting on the output.

Can async components use caching?

Yes. The fetch API with Next.js Caching options controls caching behavior. Database queries can be cached at the application level with memoization.

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