Component Types
async function ServerComponent() {
const data = await fetchData();
return <div>{data.title}</div>;
}
'use client';
function ClientComponent() {
const [state, setState] = useState();
return <button onClick={() => setState('clicked')}>Click</button>;
}
function ParentComponent() {
return (
<div>
<ServerComponent />
<ClientComponent />
</div>
);
}
File Structure
export default async function Page() {
return <ServerComponent />;
}
'use client';
export default function ClientButton() {
return <button>Click me</button>;
}
export default async function ServerData() {
const data = await fetch('/api/data');
return <div>{data.title}</div>;
}
Server-Side Data Fetching
async function UserProfile({ userId }) {
const user = await db.users.findUnique({
where: { id: userId }
});
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
async function ProductList() {
const products = await fetch('https://api.example.com/products', {
cache: 'force-cache'
}).then(res => res.json());
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
Caching Strategies
const data = await fetch('/api/data');
const data = await fetch('/api/data', {
next: { revalidate: 60 }
});
const data = await fetch('/api/data', {
cache: 'no-store'
});
const data = await fetch('/api/data', {
next: { tags: ['products'] }
});
revalidateTag('products');
Parallel Data Fetching
async function Dashboard() {
const [users, posts, analytics] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/analytics')
]);
const [usersData, postsData, analyticsData] = await Promise.all([
users.json(),
posts.json(),
analytics.json()
]);
return (
<div>
<UserList users={usersData} />
<PostList posts={postsData} />
<Analytics data={analyticsData} />
</div>
);
}
Serializable Props
async function ProductCard({ productId, category }) {
const product = await getProduct(productId);
return (
<div>
<h2>{product.name}</h2>
<p>{product.description}</p>
<ClientButton productId={productId} /> {/* Pass serializable data */}
</div>
);
}
'use client';
function ClientButton({ productId }) {
const handleClick = () => {
addToCart(productId);
};
return <button onClick={handleClick}>Add to Cart</button>;
}
Non-Serializable Props
async function ServerComponent() {
const data = await fetchData();
return <ClientComponent data={data} />;
}
'use client';
function ClientComponent({ data }) {
const handleAction = () => {
};
return (
<div>
<p>{data.title}</p>
<button onClick={handleAction}>Action</button>
</div>
);
}
Server-Client Boundary
async function ProductPage({ productId }) {
const product = await getProduct(productId);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* Client boundary for interactive features */}
<ClientProductActions product={product} />
</div>
);
}
'use client';
function ClientProductActions({ product }) {
const [quantity, setQuantity] = useState(1);
return (
<div>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(parseInt(e.target.value))}
/>
<button onClick={() => addToCart(product.id, quantity)}>
Add to Cart
</button>
</div>
);
}
Conditional Rendering
async function ConditionalComponent({ userId }) {
const user = await getUser(userId);
if (!user) {
return <div>User not found</div>;
}
if (user.role === 'admin') {
return <AdminDashboard user={user} />;
}
return <UserDashboard user={user} />;
}
Error Boundaries
async function SafeDataFetching() {
try {
const data = await fetch('/api/unreliable-endpoint');
return <DataDisplay data={data} />;
} catch (error) {
return <ErrorFallback error={error.message} />;
}
}
'use client';
function ClientErrorBoundary({ children }) {
return (
<ErrorBoundary fallback={<div>Something went wrong</div>}>
{children}
</ErrorBoundary>
);
}
Server Component Layouts
async function ProductLayout({ children }) {
const categories = await getCategories();
return (
<div className="product-layout">
<aside>
<CategoryNav categories={categories} />
</aside>
<main>
{children}
</main>
</div>
);
}
export default async function ProductPage() {
return (
<ProductLayout>
<ProductList />
</ProductLayout>
);
}
CSS-in-JS Considerations
import styles from './ServerComponent.module.css';
async function ServerComponent() {
return (
<div className={styles.container}>
<h1 className={styles.title}>Server Rendered</h1>
</div>
);
}
'use client';
import styled from 'styled-components';
const StyledButton = styled.button`
background: blue;
color: white;
`;
function ClientComponent() {
return <StyledButton>Styled Button</StyledButton>;
}
Streaming and Suspense
async function StreamingPage() {
return (
<div>
<Suspense fallback={<div>Loading header...</div>}>
<Header />
</Suspense>
<Suspense fallback={<div>Loading content...</div>}>
<SlowContent />
</Suspense>
<Suspense fallback={<div>Loading footer...</div>}>
<Footer />
</Suspense>
</div>
);
}
async function SlowContent() {
await new Promise(resolve => setTimeout(resolve, 2000));
const data = await fetch('/api/slow-data');
return <div>{data.content}</div>;
}
Preloading Data
async function ProductList() {
const productsPromise = getProducts();
return (
<div>
<h1>Products</h1>
<Suspense fallback={<ProductSkeleton />}>
<ProductGrid productsPromise={productsPromise} />
</Suspense>
</div>
);
}
async function ProductGrid({ productsPromise }) {
const products = await productsPromise;
return (
<div className="grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
Server State vs Client State
async function UserProfile({ userId }) {
const user = await getUser(userId);
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
{/* Pass server state to client component */}
<ClientUserActions user={user} />
</div>
);
}
'use client';
function ClientUserActions({ user }) {
const [isEditing, setIsEditing] = useState(false);
const [localName, setLocalName] = useState(user.name);
return (
<div>
{isEditing ? (
<input
value={localName}
onChange={(e) => setLocalName(e.target.value)}
/>
) : (
<p>{localName}</p>
)}
<button onClick={() => setIsEditing(!isEditing)}>
{isEditing ? 'Save' : 'Edit'}
</button>
</div>
);
}
Server Actions
async function createPost(formData) {
'use server';
const title = formData.get('title');
const content = formData.get('content');
const post = await db.posts.create({
data: { title, content }
});
revalidatePath('/posts');
redirect('/posts');
}
'use client';
import { createPost } from './actions';
function CreatePostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="Post title" />
<textarea name="content" placeholder="Post content" />
<button type="submit">Create Post</button>
</form>
);
}
Component Design
async function ProductPage({ productId }) {
const product = await getProduct(productId);
return (
<div>
<ProductHeader product={product} />
<ProductDetails product={product} />
<ClientProductActions product={product} />
</div>
);
}
'use client';
function ClientProductActions({ product }) {
const [quantity, setQuantity] = useState(1);
return (
<div>
<QuantitySelector value={quantity} onChange={setQuantity} />
<AddToCartButton productId={product.id} quantity={quantity} />
</div>
);
}
Error Handling
async function SafeServerComponent() {
try {
const data = await fetch('/api/data');
return <DataDisplay data={data} />;
} catch (error) {
console.error('Server error:', error);
return <ErrorFallback />;
}
}
'use client';
function ClientErrorBoundary({ children }) {
return (
<ErrorBoundary
fallback={<div>Something went wrong in client</div>}
onError={(error) => {
// Log client errors
console.error('Client error:', error);
}}
>
{children}
</ErrorBoundary>
);
}
Performance Tips
import { cache } from 'react';
const getExpensiveData = cache(async (id) => {
return await expensiveOperation(id);
});
async function CachedComponent({ id }) {
const data = await getExpensiveData(id);
return <div>{data.result}</div>;
}
async function OptimizedPage() {
const [users, posts, settings] = await Promise.all([
getUsers(),
getPosts(),
getSettings()
]);
return (
<div>
<UserList users={users} />
<PostList posts={posts} />
<SettingsPanel settings={settings} />
</div>
);
}
Security Considerations
async function UserProfile({ userId }) {
if (!isValidUserId(userId)) {
throw new Error('Invalid user ID');
}
const user = await getUser(userId);
const safeUser = {
id: user.id,
name: user.name,
email: user.email
};
return <ClientUserProfile user={safeUser} />;
}
'use client';
function ClientUserProfile({ user }) {
const handleUpdate = async (formData) => {
const name = formData.get('name');
if (!name || name.length < 2) {
alert('Name must be at least 2 characters');
return;
}
await updateUser(formData);
};
return (
<form action={handleUpdate}>
<input name="name" defaultValue={user.name} />
<button type="submit">Update</button>
</form>
);
}