Fetch Architecture for Next.js

VerifiedSafe

Provides client and server-side fetch utilities for Next.js applications, including API route proxying to FastAPI backends. Handles authentication token refresh, server-side data fetching, and error handling across layers. Use this skill when asked to set up fetch utilities or implement data fetching with auth in Next.js.

Sby Skills Guide Bot
DevelopmentIntermediate
6/2/2026
Claude CodeCursor
#nextjs#fetch-utilities#api-proxy#authentication#fastapi

Recommended for

Automated summary

Sets up client and server-side fetch utilities for Next.js with API route proxying to FastAPI backends and authentication token handling.

Strengths

  • Clear separation of client and server fetch logic
  • Automatic token refresh handling
  • Easy-to-configure API route proxying

Limitations

  • Requires a FastAPI backend specifically
  • Presumes a predefined project structure
  • May be overkill for simple projects without authentication
When to use it

Use this skill when you need to build a robust fetch architecture for a Next.js application paired with a FastAPI backend and token-based authentication.

When not to use it

Do not use this skill for projects without a backend or when you do not need API proxying or authentication token management.

Security analysis

Safe
Quality score85/100

The skill provides legitimate client and server-side fetch utility code for Next.js with standard authentication handling. It includes no destructive commands, obfuscated payloads, or instruction to exfiltrate data. All network operations are within the application's normal flow.

No concerns found

How this analysis is made

Examples

Set up fetch utilities
Set up client and server fetch utilities for my Next.js app that proxies to a FastAPI backend at http://localhost:8000/api/v1, with auth token refresh.
Add API route proxy
Create an API route in Next.js that proxies requests to the FastAPI backend, including authentication headers.
Implement auth refresh
Implement automatic token refresh in the client-side fetch for my Next.js app when the access token expires.

Fetch Architecture Skill

Client and server-side fetch utilities for Next.js applications with API route proxying to FastAPI backends.

When to Use This Skill

Use this skill when asked to:

  • Set up fetch utilities for Next.js
  • Configure client-side API calls with auth refresh
  • Implement server-side data fetching
  • Create API route proxies to backend services
  • Handle authentication tokens across layers

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    Browser (Client)                          │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Client Components                                   │    │
│  │  • fetchClient.get/post/put/delete                  │    │
│  │  • SWR hooks with fetcher                           │    │
│  └──────────────────────────┬──────────────────────────┘    │
└─────────────────────────────┼───────────────────────────────┘
                              │ HTTP (cookies)
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Next.js Server                            │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  API Routes (app/api/...)                           │    │
│  │  • withAuth() wrapper                               │    │
│  │  • backendGet/Post/Put/Delete helpers               │    │
│  └──────────────────────────┬──────────────────────────┘    │
│                             │                                │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Server Actions                                     │    │
│  │  • serverGet/Post/Put/Delete                        │    │
│  │  • Forwards cookies to API routes                   │    │
│  └──────────────────────────┬──────────────────────────┘    │
│                             │                                │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Server Components (pages)                          │    │
│  │  • auth() session check                             │    │
│  │  • Call server actions for SSR data                 │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────┼───────────────────────────────┘
                              │ HTTP (Bearer token)
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    FastAPI Backend                           │
│  /api/v1/...                                                │
└─────────────────────────────────────────────────────────────┘

Directory Structure

lib/
├── fetch/
│   ├── index.ts              # Exports
│   ├── client.ts             # Client-side fetch (browser)
│   ├── server.ts             # Server-side fetch (actions, routes)
│   ├── api-route-helper.ts   # API route wrappers
│   ├── errors.ts             # Error classes
│   └── types.ts              # TypeScript types
└── auth/
    ├── server-auth.ts        # Server authentication
    └── auth-service.ts       # Client auth (token refresh)

Core Files

1. Error Classes

// lib/fetch/errors.ts
export class ApiError extends Error {
  constructor(
    message: string,
    public status: number,
    public data?: unknown
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

export function extractErrorMessage(data: unknown): string {
  if (typeof data === 'string') return data;
  if (typeof data === 'object' && data !== null) {
    const obj = data as Record<string, unknown>;
    if (typeof obj.detail === 'string') return obj.detail;
    if (typeof obj.message === 'string') return obj.message;
    if (typeof obj.error === 'string') return obj.error;
  }
  return 'An error occurred';
}

2. Type Definitions

// lib/fetch/types.ts
export interface FetchOptions {
  headers?: Record<string, string>;
  timeout?: number;
}

export interface FetchRequestOptions extends FetchOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
  body?: unknown;
}

3. Client Fetch (Browser)

// lib/fetch/client.ts
"use client";

import { AuthService } from '@/lib/auth/auth-service';
import { ApiError, extractErrorMessage } from './errors';
import type { FetchOptions, FetchRequestOptions } from './types';

const DEFAULT_TIMEOUT = 30000;
const MAX_RETRIES = 2;

async function clientFetch<T>(
  url: string,
  options: FetchRequestOptions = {},
  attempt = 1,
  isRetryAfterRefresh = false
): Promise<T> {
  const controller = new AbortController();
  const timeoutId = setTimeout(
    () => controller.abort(),
    options.timeout || DEFAULT_TIMEOUT
  );

  try {
    const response = await fetch(url, {
      method: options.method || 'GET',
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
      body: options.body ? JSON.stringify(options.body) : undefined,
      signal: controller.signal,
      credentials: 'include',  // Include cookies
    });

    const data = await response.json().catch(() => ({}));

    if (!response.ok) {
      // Handle 401 - try token refresh
      if (response.status === 401 && !isRetryAfterRefresh) {
        clearTimeout(timeoutId);
        const newToken = await AuthService.refreshAccessToken();
        if (newToken) {
          return clientFetch<T>(url, options, attempt, true);
        }
        window.location.href = '/login';
        throw new ApiError('Session expired', 401);
      }

      // Retry on 429/503
      if ((response.status === 429 || response.status === 503) && attempt < MAX_RETRIES) {
        clearTimeout(timeoutId);
        await new Promise(r => setTimeout(r, 1000 * attempt));
        return clientFetch<T>(url, options, attempt + 1);
      }

      throw new ApiError(extractErrorMessage(data), response.status, data);
    }

    return data as T;
  } finally {
    clearTimeout(timeoutId);
  }
}

// Legacy wrapper (returns { data: T })
export const fetchClient = {
  get: async <T>(url: string, opts?: FetchOptions) => {
    const data = await clientFetch<T>(url, { ...opts, method: 'GET' });
    return { data };
  },
  post: async <T>(url: string, body?: unknown, opts?: FetchOptions) => {
    const data = await clientFetch<T>(url, { ...opts, method: 'POST', body });
    return { data };
  },
  put: async <T>(url: string, body?: unknown, opts?: FetchOptions) => {
    const data = await clientFetch<T>(url, { ...opts, method: 'PUT', body });
    return { data };
  },
  delete: async <T>(url: string, opts?: FetchOptions) => {
    const data = await clientFetch<T>(url, { ...opts, method: 'DELETE' });
    return { data };
  },
};

4. Server Fetch (Actions & Routes)

// lib/fetch/server.ts
"use server";

import { cookies, headers } from 'next/headers';
import { ApiError, extractErrorMessage } from './errors';
import type { FetchRequestOptions } from './types';

// Server → Next.js API routes
export async function serverFetch<T>(
  url: string,
  options: FetchRequestOptions = {}
): Promise<T> {
  const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
  const cookieStore = await cookies();
  const cookieHeader = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');

  const response = await fetch(`${baseUrl}${url}`, {
    method: options.method || 'GET',
    headers: {
      'Content-Type': 'application/json',
      ...(cookieHeader && { Cookie: cookieHeader }),
      ...options.headers,
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  const data = await response.json().catch(() => ({}));
  if (!response.ok) {
    throw new ApiError(extractErrorMessage(data), response.status, data);
  }
  return data as T;
}

// API routes → FastAPI backend
export async function backendFetch<T>(
  url: string,
  token: string,
  options: FetchRequestOptions = {}
): Promise<T> {
  const baseUrl = process.env.NEXT_PUBLIC_BACKEND_API_URL || 'http://localhost:8000';

  const response = await fetch(`${baseUrl}${url}`, {
    method: options.method || 'GET',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
      ...options.headers,
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  const data = await response.json().catch(() => ({}));
  if (!response.ok) {
    throw new ApiError(extractErrorMessage(data), response.status, data);
  }
  return data as T;
}

// Convenience methods
export const serverGet = <T>(url: string) => serverFetch<T>(url, { method: 'GET' });
export const serverPost = <T>(url: string, body: unknown) => serverFetch<T>(url, { method: 'POST', body });
export const serverPut = <T>(url: string, body: unknown) => serverFetch<T>(url, { method: 'PUT', body });
export const serverDelete = <T>(url: string) => serverFetch<T>(url, { method: 'DELETE' });

5. API Route Helper

// lib/fetch/api-route-helper.ts
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth/server-auth';
import { backendFetch } from './server';
import { ApiError } from './errors';

export async function withAuth<T>(
  handler: (token: string) => Promise<T>
): Promise<NextResponse> {
  try {
    const session = await auth();
    if (!session?.accessToken) {
      return NextResponse.json({ detail: 'Unauthorized' }, { status: 401 });
    }
    const data = await handler(session.accessToken);
    return NextResponse.json(data);
  } catch (error) {
    if (error instanceof ApiError) {
      return NextResponse.json({ detail: error.message }, { status: error.status });
    }
    return NextResponse.json({ detail: 'Internal server error' }, { status: 500 });
  }
}

export const backendGet = <T>(url: string, token: string) =>
  backendFetch<T>(url, token, { method: 'GET' });
export const backendPost = <T>(url: string, token: string, body: unknown) =>
  backendFetch<T>(url, token, { method: 'POST', body });
export const backendPut = <T>(url: string, token: string, body: unknown) =>
  backendFetch<T>(url, token, { method: 'PUT', body });
export const backendDelete = <T>(url: string, token: string) =>
  backendFetch<T>(url, token, { method: 'DELETE' });

Request Flow

Client-Side (Mutations)

Component → fetchClient → API Route → withAuth → backendFetch → FastAPI

Server-Side (SSR)

Page → Server Action → serverFetch → API Route → withAuth → backendFetch → FastAPI

SWR (Data Fetching)

useSWR(url, fetcher) → fetchClient.get → API Route → withAuth → backendFetch → FastAPI

Key Patterns

  1. Client includes cookies - credentials: 'include'
  2. Server forwards cookies - Cookie header to API routes
  3. API routes use Bearer token - Extract from session
  4. Auto token refresh - On 401, try refresh once
  5. Consistent error format - ApiError class
  6. Retry on rate limit - 429/503 with backoff

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/fetch-architecture-for-next-js-mm5t8abr/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/fetch-architecture-for-next-js-mm5t8abr' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/fetch-architecture-for-next-js-mm5t8abr/download' --output '.skill-downloads/fetch-architecture-for-next-js-mm5t8abr/SKILL.md.part' && mv '.skill-downloads/fetch-architecture-for-next-js-mm5t8abr/SKILL.md.part' '.skill-downloads/fetch-architecture-for-next-js-mm5t8abr/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

This file does not provide a usable name/description header for this native procedure. It remains available to download and inspect; use the manual method below.

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/fetch-architecture-for-next-js-mm5t8abr/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills