# Frontend Next API Layer

> Next.js dual API layer: server fetch with ISR in lib/api for RSC, client api utility with cookie auth for services and React Query. Use when making API calls, fetching data in Server or Client Components.

- Skill: `xmuhameed/frontend-next-api-layer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add xmuhameed/frontend-next-api-layer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xmuhameed/frontend-next-api-layer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: xmuhameed (https://skillmd.com/u/xmuhameed)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/xmuhameed/frontend-next-api-layer

---


# Next.js API Layer

## Two Layers — Never Mix

| Context | Module | Pattern |
|---------|--------|---------|
| Server Components (RSC/ISR) | `lib/api/*.ts` | `fetch()` + `next: { revalidate }` |
| Client + hooks | `lib/api-client.ts` or `utils/api.ts` | Cookie auth, throws on error |

## Server Fetch

```typescript
// lib/api/product.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;

export async function getProductBySlug(slug: string) {
  const res = await fetch(`${API_BASE_URL}/product-variation/get-product-by-slug?slug=${slug}`, {
    next: { revalidate: 300 },
  });
  return res.json();
}
```

Use in: `page.tsx`, `generateMetadata`, `generateStaticParams`. **No cookie auth** on server.

## Client API

```typescript
// utils/api.ts or lib/api-client.ts
export const api = {
  async get<T>(path: string, options?) {
    const token = Cookies.get('token');
    const res = await fetch(`${API_BASE_URL}${path}`, {
      headers: { Authorization: token ? `Bearer ${token}` : '', ...capiHeaders() },
      credentials: 'include',
    });
    const json = await res.json();
    if (json.key || json.message) throw new Error(json.message);
    return json as ApiResponse<T>;
  },
};
```

## Service Layer

```typescript
// services/products.service.ts
export const productsService = {
  async getAll(params: ProductsParams) {
    const res = await api.get<Product[]>(`/product/get-all-products?${qs}`);
    return { products: res.data, totalCount: res.count };
  },
};

export const productsKeys = {
  all: ['products'] as const,
  list: (p) => [...productsKeys.all, 'list', p] as const,
};
```

## React Query Hook

```typescript
export const useProducts = (params) =>
  useQuery({
    queryKey: productsKeys.list(params),
    queryFn: () => productsService.getAll(params),
    staleTime: 2 * 60 * 1000,
  });
```

## Response Shape (backend)

```typescript
{ status: 'success', data: T, count?: number }
```

## Route Handlers (BFF)

`app/api/ae-catalog/route.ts` — server-side CSV feeds. Not for regular CRUD.

## Rules

- Server page fetches initial data → pass as props to Client
- Client refetches via React Query for filters/infinite scroll
- Never call `Cookies.get` in Server Components

