Next.js + Mock API: интеграция для SSR и SSG

← Вернуться к статьям

Введение

Next.js — один из самых популярных React фреймворков для создания full-stack приложений с поддержкой SSR (Server-Side Rendering) и SSG (Static Site Generation). Но что делать, если backend еще не готов, а нужно начать разработку с SSR и SSG?

В этом туториале вы научитесь интегрировать Next.js с Mock API для работы с SSR и SSG. Мы рассмотрим все способы работы с данными в Next.js: getServerSideProps, getStaticProps, API Routes и клиентские запросы с SWR и React Query.

✅ Что вы получите в конце:

💡 Для кого этот туториал:

📋 План действий (15 минут)

Что такое Next.js SSR и SSG? 🚀

Next.js поддерживает три способа рендеринга:

Тип рендеринга Описание Когда использовать
SSR (Server-Side Rendering) Страница генерируется на сервере при каждом запросе Динамический контент, персональные данные
SSG (Static Site Generation) Страница генерируется на этапе сборки Статический контент, блог, документация
CSR (Client-Side Rendering) Страница рендерится в браузере Интерактивные части, дашборды

Data Fetching методы в Next.js:

Шаг 1: Setup проекта (2 минуты)

1 Создайте Next.js приложение

# Создаем Next.js проект с TypeScript
npx create-next-app@latest my-app --typescript --tailwind --app

# Или с Pages Router
npx create-next-app@latest my-app --typescript --tailwind

# Переходим в директорию
cd my-app

# Устанавливаем дополнительные зависимости
npm install swr axios

2 Структура проекта

my-app/
├── pages/                    # Pages Router
│   ├── api/                  # API Routes
│   │   └── todos/
│   │       └── [id].ts
│   ├── _app.tsx             # Главный компонент
│   ├── index.tsx             # Главная страница
│   └── todos/
│       ├── index.tsx         # Список todos
│       └── [id].tsx          # Детали todo
├── lib/
│   └── api.ts                # API клиент
├── types/
│   └── todo.ts               # TypeScript типы
├── .env.local                # Переменные окружения
└── next.config.js

# Или с App Router
my-app/
├── app/
│   ├── api/                  # API Routes
│   ├── page.tsx              # Главная страница
│   └── todos/
│       └── page.tsx
├── lib/
│   └── api.ts
└── types/
    └── todo.ts

Шаг 2: Создание Mock API (3 минуты)

1 Зарегистрируйтесь на LightBox API

Перейдите на lightboxapi.ru и создайте бесплатный аккаунт.

2 Создайте workspace

В dashboard создайте новый workspace с именем nextjs-todos.

3 Настройте endpoints

Endpoint 1: GET /todos
{
  "todos": [
    {
      "id": "1",
      "title": "Изучить Next.js SSR",
      "completed": false,
      "createdAt": "2025-10-28T10:00:00Z"
    },
    {
      "id": "2",
      "title": "Интегрировать Mock API",
      "completed": true,
      "createdAt": "2025-10-28T09:00:00Z"
    }
  ]
}
Endpoint 2: GET /todos/:id
{
  "id": "{params.id}",
  "title": "Todo title",
  "completed": false,
  "createdAt": "2025-10-28T10:00:00Z"
}
Endpoint 3: POST /todos
{
  "id": "{randomUUID}",
  "title": "{body.title}",
  "completed": false,
  "createdAt": "{$timestamp}"
}

Скопируйте URL вашего Mock API: https://lightboxapi.ru/your-workspace

Шаг 3: Универсальный API клиент (2 минуты)

В Next.js нужно создавать API клиент, который работает как на сервере (в getServerSideProps/getStaticProps), так и на клиенте. Важно определить базовый URL правильно.

1 Создайте TypeScript типы

Файл: types/todo.ts

export interface Todo {
  id: string;
  title: string;
  completed: boolean;
  createdAt: string;
}

export interface CreateTodoDto {
  title: string;
}

2 Создайте универсальный API клиент

Файл: lib/api.ts

import type { Todo, CreateTodoDto } from '../types/todo';

// Определяем базовый URL
const getBaseUrl = () => {
  // На сервере используем полный URL
  if (typeof window === 'undefined') {
    return process.env.NEXT_PUBLIC_API_URL || 'https://lightboxapi.ru/your-workspace';
  }
  // На клиенте используем относительный или полный URL
  return process.env.NEXT_PUBLIC_API_URL || 'https://lightboxapi.ru/your-workspace';
};

const API_BASE_URL = getBaseUrl();

// API клиент
export const api = {
  // Получить все todos
  async getTodos(): Promise {
    const response = await fetch(`${API_BASE_URL}/todos`);
    if (!response.ok) {
      throw new Error('Failed to fetch todos');
    }
    const data = await response.json();
    return data.todos || data;
  },

  // Получить todo по ID
  async getTodo(id: string): Promise {
    const response = await fetch(`${API_BASE_URL}/todos/${id}`);
    if (!response.ok) {
      throw new Error('Failed to fetch todo');
    }
    return response.json();
  },

  // Создать todo
  async createTodo(data: CreateTodoDto): Promise {
    const response = await fetch(`${API_BASE_URL}/todos`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
    if (!response.ok) {
      throw new Error('Failed to create todo');
    }
    return response.json();
  },

  // Обновить todo
  async updateTodo(id: string, data: Partial): Promise {
    const response = await fetch(`${API_BASE_URL}/todos/${id}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
    if (!response.ok) {
      throw new Error('Failed to update todo');
    }
    return response.json();
  },

  // Удалить todo
  async deleteTodo(id: string): Promise {
    const response = await fetch(`${API_BASE_URL}/todos/${id}`, {
      method: 'DELETE',
    });
    if (!response.ok) {
      throw new Error('Failed to delete todo');
    }
  },
};

3 Настройте переменные окружения

Файл: .env.local

NEXT_PUBLIC_API_URL=https://lightboxapi.ru/your-workspace

⚠️ Важно:

В Next.js переменные окружения для клиента должны начинаться с NEXT_PUBLIC_. Иначе они не будут доступны на клиенте.

Шаг 4: getServerSideProps + Mock API (2 минуты)

getServerSideProps выполняется на сервере при каждом запросе. Идеально для динамического контента, который зависит от пользователя или времени.

1 Создайте страницу с SSR

Файл: pages/todos/index.tsx

import { GetServerSideProps } from 'next';
import type { Todo } from '../../types/todo';
import { api } from '../../lib/api';

interface TodosPageProps {
  todos: Todo[];
}

export default function TodosPage({ todos }: TodosPageProps) {
  return (
    

Todos

    {todos.map((todo) => (
  • {todo.title} - {todo.completed ? '✅' : '⭕'}
  • ))}
); } // SSR: Выполняется на сервере при каждом запросе export const getServerSideProps: GetServerSideProps = async () => { try { const todos = await api.getTodos(); return { props: { todos, }, }; } catch (error) { console.error('Error fetching todos:', error); return { props: { todos: [], }, }; } };

2 Динамическая страница с SSR

Файл: pages/todos/[id].tsx

import { GetServerSideProps } from 'next';
import type { Todo } from '../../types/todo';
import { api } from '../../lib/api';

interface TodoPageProps {
  todo: Todo;
}

export default function TodoPage({ todo }: TodoPageProps) {
  return (
    

{todo.title}

Status: {todo.completed ? 'Completed' : 'Pending'}

Created: {new Date(todo.createdAt).toLocaleString()}

); } export const getServerSideProps: GetServerSideProps = async (context) => { const { id } = context.params!; try { const todo = await api.getTodo(id as string); return { props: { todo, }, }; } catch (error) { return { notFound: true, // Вернуть 404 }; } };

Шаг 5: getStaticProps + Mock API (2 минуты)

getStaticProps выполняется на этапе сборки (build time). Страницы генерируются статически и могут быть закэшированы. Идеально для блогов, документации и контента, который редко меняется.

1 SSG с getStaticProps

Файл: pages/todos/index.tsx

import { GetStaticProps } from 'next';
import type { Todo } from '../../types/todo';
import { api } from '../../lib/api';

interface TodosPageProps {
  todos: Todo[];
}

export default function TodosPage({ todos }: TodosPageProps) {
  return (
    

Todos (Static Generated)

    {todos.map((todo) => (
  • {todo.title}
  • ))}
); } // SSG: Выполняется на этапе сборки export const getStaticProps: GetStaticProps = async () => { try { const todos = await api.getTodos(); return { props: { todos, }, // Revalidate каждые 60 секунд (Incremental Static Regeneration) revalidate: 60, }; } catch (error) { return { props: { todos: [], }, }; } };

2 SSG с динамическими путями

Файл: pages/todos/[id].tsx

import { GetStaticPaths, GetStaticProps } from 'next';
import type { Todo } from '../../types/todo';
import { api } from '../../lib/api';

interface TodoPageProps {
  todo: Todo;
}

export default function TodoPage({ todo }: TodoPageProps) {
  return (
    

{todo.title}

Status: {todo.completed ? 'Completed' : 'Pending'}

); } // Определяем какие пути генерировать export const getStaticPaths: GetStaticPaths = async () => { try { const todos = await api.getTodos(); const paths = todos.map((todo) => ({ params: { id: todo.id }, })); return { paths, fallback: 'blocking', // или 'true' для ISR }; } catch (error) { return { paths: [], fallback: 'blocking', }; } }; // Генерируем страницу для каждого пути export const getStaticProps: GetStaticProps = async (context) => { const { id } = context.params!; try { const todo = await api.getTodo(id as string); return { props: { todo, }, revalidate: 60, // ISR: обновлять каждые 60 секунд }; } catch (error) { return { notFound: true, }; } };

💡 Fallback варианты:

Шаг 6: SWR интеграция (2 минуты)

SWR (stale-while-revalidate) — библиотека от создателей Next.js для клиентских запросов. Она обеспечивает кэширование, ревалидацию и автоматическое обновление данных.

1 Настройте SWR Provider

Файл: pages/_app.tsx

import type { AppProps } from 'next/app';
import { SWRConfig } from 'swr';

function MyApp({ Component, pageProps }: AppProps) {
  return (
     fetch(url).then((res) => res.json()),
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
      }
    >
      
    
  );
}

export default MyApp;

2 Используйте SWR в компонентах

Файл: pages/todos/index.tsx

import useSWR from 'swr';
import { api } from '../../lib/api';
import type { Todo } from '../../types/todo';

export default function TodosPage() {
  const { data, error, isLoading, mutate } = useSWR(
    '/api/todos',
    () => api.getTodos()
  );

  if (isLoading) return 
Loading...
; if (error) return
Error: {error.message}
; const handleCreate = async () => { const newTodo = await api.createTodo({ title: 'New Todo' }); // Обновить кэш SWR mutate(); }; return (

Todos (Client-Side)

    {data?.map((todo) => (
  • {todo.title}
  • ))}
); }

3 Комбинация SSR + SWR

Лучший подход: предзагрузить данные через SSR, затем использовать SWR для обновлений.

import { GetServerSideProps } from 'next';
import useSWR from 'swr';
import { api } from '../../lib/api';
import type { Todo } from '../../types/todo';

interface TodosPageProps {
  initialTodos: Todo[];
}

export default function TodosPage({ initialTodos }: TodosPageProps) {
  // Используем initialTodos как fallback для SWR
  const { data: todos = initialTodos, mutate } = useSWR(
    '/api/todos',
    () => api.getTodos(),
    {
      fallbackData: initialTodos, // Начальные данные из SSR
    }
  );

  return (
    

Todos (SSR + Client Updates)

    {todos.map((todo) => (
  • {todo.title}
  • ))}
); } export const getServerSideProps: GetServerSideProps = async () => { const initialTodos = await api.getTodos(); return { props: { initialTodos } }; };

Шаг 7: API Routes в Next.js (1 минута)

API Routes позволяют создавать API endpoints прямо в Next.js приложении. Это удобно для создания прокси к внешнему Mock API или добавления серверной логики.

1 Создайте API Route

Файл: pages/api/todos/index.ts

import type { NextApiRequest, NextApiResponse } from 'next';
import { api } from '../../../lib/api';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    try {
      const todos = await api.getTodos();
      res.status(200).json(todos);
    } catch (error) {
      res.status(500).json({ error: 'Failed to fetch todos' });
    }
  } else if (req.method === 'POST') {
    try {
      const todo = await api.createTodo(req.body);
      res.status(201).json(todo);
    } catch (error) {
      res.status(500).json({ error: 'Failed to create todo' });
    }
  } else {
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

2 Динамический API Route

Файл: pages/api/todos/[id].ts

import type { NextApiRequest, NextApiResponse } from 'next';
import { api } from '../../../lib/api';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { id } = req.query;

  if (req.method === 'GET') {
    try {
      const todo = await api.getTodo(id as string);
      res.status(200).json(todo);
    } catch (error) {
      res.status(404).json({ error: 'Todo not found' });
    }
  } else if (req.method === 'PUT') {
    try {
      const todo = await api.updateTodo(id as string, req.body);
      res.status(200).json(todo);
    } catch (error) {
      res.status(500).json({ error: 'Failed to update todo' });
    }
  } else if (req.method === 'DELETE') {
    try {
      await api.deleteTodo(id as string);
      res.status(204).end();
    } catch (error) {
      res.status(500).json({ error: 'Failed to delete todo' });
    }
  } else {
    res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

App Router (Next.js 13+) 🆕

В Next.js 13+ появился новый App Router. Работа с данными немного отличается.

1 Server Components с App Router

Файл: app/todos/page.tsx

import { api } from '@/lib/api';
import type { Todo } from '@/types/todo';

// Server Component по умолчанию
export default async function TodosPage() {
  // Прямой fetch на сервере
  const todos = await api.getTodos();

  return (
    

Todos (Server Component)

    {todos.map((todo) => (
  • {todo.title}
  • ))}
); }

2 Client Components с SWR

Файл: app/todos/page.tsx

'use client'; // Указываем, что это Client Component

import useSWR from 'swr';
import { api } from '@/lib/api';
import type { Todo } from '@/types/todo';

export default function TodosPage() {
  const { data, error, isLoading } = useSWR(
    'todos',
    () => api.getTodos()
  );

  if (isLoading) return 
Loading...
; if (error) return
Error: {error.message}
; return (

Todos (Client Component)

    {data?.map((todo) => (
  • {todo.title}
  • ))}
); }

Middleware для защиты API Routes 🛡️

Middleware в Next.js позволяет перехватывать запросы и добавлять логику, например, проверку аутентификации или rate limiting.

// Файл: middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Защита API Routes
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const apiKey = request.headers.get('X-API-Key');

    if (!apiKey || apiKey !== process.env.API_KEY) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      );
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

Best Practices для Next.js + Mock API 🌟

✅ Рекомендации:

Deploy на Vercel 🚀

1 Подготовьте проект

# Соберите проект
npm run build

# Проверьте, что сборка прошла успешно
npm run start

2 Deploy на Vercel

  1. Подключите GitHub репозиторий к Vercel
  2. Добавьте переменную окружения NEXT_PUBLIC_API_URL в настройках проекта
  3. Vercel автоматически определит Next.js и выполнит сборку
  4. Ваше приложение будет доступно по URL от Vercel

Заключение

Вы научились интегрировать Next.js с Mock API для всех сценариев рендеринга: SSR, SSG и клиентские запросы. Это позволяет начать разработку сразу, без ожидания backend.

💡 Что мы изучили:

Создайте Mock API за 2 минуты

Начните разработку Next.js приложения с SSR и SSG прямо сейчас с LightBox API. Не ждите backend — создайте Mock API за 2 минуты и начните разработку.

Попробовать бесплатно →
← Вернуться к статьям