Skip to main content

Command Palette

Search for a command to run...

Type-Safe API Fetching in Next.js Server Components

ing any for API responses in Next.js might save time upfront, but it compromises your app's stability. Let’s look at why it’s dangerous and how to implement clean, production-ready type safety in Next.js Server Components.

Updated
2 min read
A
Full-Stack Web Developer passionate about architecting scalable web applications and clean code. Formally trained via Programming Hero, specializing in Next.js, Node.js, and PostgreSQL. Experienced in agile team collaboration, project management, and transforming complex backend logic into fluid user experiences. Writing about web development, databases, and modern tech stacks.

Why Avoid any?

When you explicitly cast an object as any, you are telling the TypeScript compiler to shut down type-checking for that entire variable branch.

TypeScript

// The Dangerous Way
const user: any = await fetchUserData();
console.log(user.profile.fullName); // No errors from TS compiler!

If the backend database changes and renames fullName to first_name, TypeScript will remain completely silent. Your code will compile without errors, but your users will face a frustrating runtime crash (TypeError: Cannot read properties of undefined).

Defining the Schema

Instead of guessing, create a dedicated data layer schema. Let's create a robust type architecture for a product catalogue system.

// types/product.ts

export interface Rating {
  rate: number;
  count: number;
}

export interface Product {
  id: number;
  title: string;
  price: number;
  description: string;
  category: string;
  image: string;
  rating: Rating; // Nested interface
}

Implementation Guide

With Next.js App Router, components default to Server Components. This means we can safely fetch data asynchronously directly inside the component function itself.

Let's fetch data cleanly using our strict contract:

// app/products/page.tsx
import { Product } from "@/types/product";
import Image from "next/image";

// We strictly declare that this function promises to return an Array of Product structures
async function fetchProducts(): Promise<Product[]> {
  const response = await fetch("https://fakestoreapi.com/products", {
    next: { revalidate: 3600 } // Cache data safely for 1 hour
  });
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const data: Product[] = await response.json();
  return data;
}

export default async function ProductsCatalogPage() {
  const products = await fetchProducts();

  return (
    <main className="max-w-7xl mx-auto p-8">
      <h1 className="text-3xl font-black mb-8">E-Commerce Catalog</h1>
      
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        {products.map((product) => (
          <div 
            key={product.id} 
            className="flex flex-col bg-white border border-slate-200 rounded-xl p-4 shadow-sm"
          >
            <div className="relative w-full h-48 mb-4">
              <Image 
                src={product.image} 
                alt={product.title}
                fill
                className="object-contain"
              />
            </div>
            {/* Intelligent autocomplete works flawlessly here */}
            <h2 className="font-bold text-lg line-clamp-1">{product.title}</h2>
            <span className="text-emerald-600 font-bold mt-1">${product.price.toFixed(2)}</span>
            <p className="text-slate-500 text-xs mt-2 line-clamp-3 flex-grow">{product.description}</p>
            
            <div className="mt-4 pt-3 border-t border-slate-100 flex justify-between items-center text-sm text-slate-600">
              <span>Rating: ⭐ {product.rating.rate}</span>
              <span>({product.rating.count} reviews)</span>
            </div>
          </div>
        ))}
      </div>
    </main>
  );
}

Main Benefits

  1. IntelliSense Autocomplete: As you write code inside .map(), your IDE will instantly suggest fields like .price, .rating.count preventing typos completely.

  2. Refactoring Safely: If you ever need to change the data structure, modifying your centralized interface will immediately highlight every file that needs fixing before deployment.