React Server Components (RSC) revolutionize data fetching by moving component rendering to the server. Combined with Laravel APIs, they create fast, efficient applications. At ZIRA Software, RSC reduced our bundle sizes by 40%.
Understanding Server Components
Traditional React (Client Components)
├── Ship all code to browser
├── Fetch data on client
├── Render on client
└── Larger JavaScript bundles
React Server Components
├── Render on server
├── Fetch data directly
├── Send HTML to client
└── Zero JavaScript for RSC
Project Architecture
├── frontend/ # Next.js 14 App Router
│ ├── app/
│ │ ├── layout.tsx # Root layout (Server)
│ │ ├── page.tsx # Home (Server)
│ │ ├── products/
│ │ │ ├── page.tsx # Products list (Server)
│ │ │ └── [id]/
│ │ │ └── page.tsx # Product detail (Server)
│ │ └── cart/
│ │ └── page.tsx # Cart (Client)
│ └── components/
│ ├── ProductCard.tsx # Server Component
│ └── AddToCart.tsx # Client Component
└── backend/ # Laravel API
└── app/Http/Controllers/
Laravel API Endpoints
// routes/api.php
Route::prefix('v1')->group(function () {
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{product}', [ProductController::class, 'show']);
Route::get('/categories', [CategoryController::class, 'index']);
});
// app/Http/Controllers/Api/ProductController.php
class ProductController extends Controller
{
public function index(Request $request)
{
$products = Product::query()
->with(['category', 'images'])
->when($request->category, fn($q, $cat) => $q->where('category_id', $cat))
->when($request->search, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
->latest()
->paginate(20);
return ProductResource::collection($products);
}
public function show(Product $product)
{
$product->load(['category', 'images', 'reviews.user']);
return new ProductResource($product);
}
}
Server Component Data Fetching
// app/products/page.tsx - Server Component
async function getProducts(searchParams: { category?: string; search?: string }) {
const params = new URLSearchParams(searchParams);
const res = await fetch(
`${process.env.API_URL}/products?${params}`,
{ next: { revalidate: 60 } }
);
if (!res.ok) throw new Error('Failed to fetch products');
return res.json();
}
export default async function ProductsPage({
searchParams,
}: {
searchParams: { category?: string; search?: string };
}) {
const { data: products, meta } = await getProducts(searchParams);
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-8">Products</h1>
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
<Pagination meta={meta} />
</div>
);
}
Server Component (No Client JS)
// components/ProductCard.tsx - Server Component
import Link from 'next/link';
import Image from 'next/image';
interface Product {
id: number;
name: string;
price: number;
image: string;
category: { name: string };
}
export default function ProductCard({ product }: { product: Product }) {
// This component ships ZERO JavaScript to the client
return (
<Link href={`/products/${product.id}`} className="group">
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
<Image
src={product.image}
alt={product.name}
width={300}
height={300}
className="object-cover group-hover:opacity-75 transition"
/>
</div>
<div className="mt-4">
<p className="text-sm text-gray-500">{product.category.name}</p>
<h3 className="text-lg font-medium">{product.name}</h3>
<p className="text-lg font-bold">${product.price}</p>
</div>
</Link>
);
}
Client Component for Interactivity
// components/AddToCart.tsx
'use client';
import { useState, useTransition } from 'react';
import { useCart } from '@/hooks/useCart';
export default function AddToCart({ productId }: { productId: number }) {
const [quantity, setQuantity] = useState(1);
const [isPending, startTransition] = useTransition();
const { addItem } = useCart();
function handleAddToCart() {
startTransition(async () => {
await addItem(productId, quantity);
});
}
return (
<div className="flex items-center gap-4">
<select
value={quantity}
onChange={(e)=> setQuantity(Number(e.target.value))}
className="border rounded px-3 py-2"
>
{[1, 2, 3, 4, 5].map((n) => (
<option key={n} value={n}>{n}</option>
))}
</select>
<button
onClick={handleAddToCart}
disabled={isPending}
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 disabled:opacity-50"
>
{isPending ? 'Adding...' : 'Add to Cart'}
</button>
</div>
);
}
Mixing Server and Client Components
// app/products/[id]/page.tsx
import { notFound } from 'next/navigation';
import AddToCart from '@/components/AddToCart';
import ReviewList from '@/components/ReviewList';
async function getProduct(id: string) {
const res = await fetch(`${process.env.API_URL}/products/${id}`, {
next: { revalidate: 60 },
});
if (!res.ok) return null;
return res.json();
}
export default async function ProductPage({
params,
}: {
params: { id: string };
}) {
const { data: product } = await getProduct(params.id);
if (!product) notFound();
return (
<div className="container mx-auto py-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
{/* Server-rendered product info */}
<div>
<img src={product.image} alt={product.name} className="rounded-lg" />
</div>
<div>
<h1 className="text-3xl font-bold">{product.name}</h1>
<p className="text-2xl font-bold mt-4">${product.price}</p>
<p className="mt-4 text-gray-600">{product.description}</p>
{/* Client component for interactivity */}
<div className="mt-8">
<AddToCart productId={product.id} />
</div>
</div>
</div>
{/* Server-rendered reviews */}
<ReviewList reviews={product.reviews} />
</div>
);
}
Streaming with Suspense
// app/products/page.tsx
import { Suspense } from 'react';
import ProductGrid from '@/components/ProductGrid';
import RecommendedProducts from '@/components/RecommendedProducts';
export default function ProductsPage() {
return (
<div>
{/* Loads first */}
<Suspense fallback={<ProductGridSkeleton />}>
<ProductGrid />
</Suspense>
{/* Streams in after */}
<Suspense fallback={<RecommendedSkeleton />}>
<RecommendedProducts />
</Suspense>
</div>
);
}
Conclusion
React Server Components with Laravel APIs create fast, efficient applications. Server-side rendering reduces JavaScript bundles while maintaining interactivity where needed.
Building modern React applications? Contact ZIRA Software for full-stack development.