Створення додатків Next.js для майбутнього з акцентом на ШІ

AINext.jsfrontendstreamingMCP
Kirill Latish
Kirill Latish
LinkedIn
Share

Дізнайтесь, як створювати додатки Next.js, які пріоритизують взаємодії з ШІ, інтегруючи потокову архітектуру та агентні компоненти UI для безшовного користувацького досвіду.

Next.js тепер розглядає агентів ШІ як користувачів першого класу в Інтернеті. Не другорядних. Не як післядумку, прикріплену через проміжне програмне забезпечення. Перший клас, поряд з людьми, які натискають кнопки в браузері. Завдяки Vercel AI SDK, рідній підтримці потоків і інтеграції Model Context Protocol, фреймворк обрав сторону. Якщо ви все ще обгортаєте Open AI чат REST-інтерфейсом і постачаєте його як вашу "функцію ШІ", вам потрібно наздоганяти.

Подумайте про те, що насправді відбувається в додатку на базі ШІ. Відповіді приходять токен за токеном, а не як готовий JSON-блок. Користувачі вводять дані природною мовою замість заповнення форм. Модель може викликати два або три інструменти під час відповіді, зупинитися, щоб запитати підтвердження, а потім продовжити генерувати. Цей робочий процес майже нічого не має спільного з циклом запит-відповідь, навколо якого ми будували протягом двадцяти років.

Цей пост охоплює те, що потрібно для створення додатків Next.js, які працюють таким чином з нуля: потокова архітектура, агентні компоненти UI та інтеграційний шар між вашим фронтендом і бекендом Laravel, який виконує важку роботу з інференції. Незалежно від того, чи ви постачаєте внутрішній copilot або продукт ШІ SaaS для клієнтів, застосовуються ті ж самі шаблони. І якщо вас також цікавить бекенд, супутня стаття про створення оркестрації агентів ШІ з Laravel глибоко розглядає цю архітектуру.

Що насправді означає акцент на ШІ для вашого фронтенду

Ми всі знаємо традиційний цикл веб-додатка. Користувач натискає кнопку. Фронтенд відправляє запит. Бекенд повертає JSON. Фронтенд його рендерить. Готово. Уся модель передбачає швидкі, дискретні, безстанційні транзакції. Додатки з акцентом на ШІ ламають цей цикл майже в кожному важливому аспекті.

Почнімо з найбільш очевидної зміни: потокового. Коли хтось запитує вашого асистента ШІ "проаналізувати дані продажів за останній квартал і запропонувати три стратегії оптимізації", модель може працювати від 10 до 30 секунд, перш ніж закінчити. Ви не можете просто показати спінер і сподіватися, що вони чекатимуть. Токени повинні з'являтися на екрані в міру їх генерації. Користувачі очікують бачити відповідь у реальному часі. Це тепер базова вимога.

Але ось частина, яку більшість команд пропускає: ваш фронтенд більше не обслуговує лише людей. Агенти ШІ також є користувачами. Вони звертаються до ваших API, знаходять ваші інструменти через сервери MCP і викликають дії програмно. Model Context Protocol (MCP) є відкритим стандартом, що робить це можливим. Відкрийте сумісну з MCP кінцеву точку з вашого додатку Next.js, і будь-який сумісний агент може переглядати ваш каталог продуктів, розміщувати замовлення або отримувати звіти без необхідності написання спеціальної інтеграції. Ніякого скрепінгу екрану. Ніяких спеціальних API-обгорток. Лише спільний протокол.

Якщо ви приймете, що агенти є реальними користувачами, наслідки каскадно зростатимуть. Ваші маршрути повинні обслуговувати HTML для браузерів і структуровані потокові дані для машин. Аутентифікація повинна працювати як для людських сесій, так і для токенів агентів. Відповіді на помилки? Вони повинні бути машинозчитуваними, а не просто "щось пішло не так" в червоному полі.

Потім є шар UX, специфічний для агентних взаємодій. Індикатори прогресу, які показують, який інструмент ШІ викликає прямо зараз. Діалоги підтвердження, які з'являються перед тим, як модель надішле електронний лист або обробить платіж від імені когось. Робочі процеси, де модель об'єднує три або чотири виклики інструментів разом, перш ніж дати остаточну відповідь. Нічого з цього не існувало в посібнику CRUD.

Діаграма нижче показує, наскільки різними насправді є ці два потоки:

Mermaid diagram is empty

Потокові відповіді ШІ в Next.js

Все інше в цій статті залежить від правильного налаштування потокового. Next.js 15 має примітиви, а Vercel AI SDK (ai та @ai-sdk/react) обгортає більшість заплутаних частин у типізований API, з яким насправді приємно працювати.

Вибір транспорту: SSE проти WebSockets

Події, надіслані сервером (SSE), є правильним за замовчуванням для більшості випадків використання потокового ШІ. Вони працюють через стандартний HTTP, проходять через CDN і проксі без проблем і автоматично перепідключаються. WebSockets мають сенс, коли вам потрібна двостороння реальна комунікація — спільне редагування, багатокористувацькі функції — але для типової схеми "користувач надсилає запит, сервер потоково передає відповідь" SSE виграє за простотою та сумісністю інфраструктури.

Vercel AI SDK використовує свій власний протокол потокових повідомлень UI під капотом, побудований на основі стандартного потокового HTTP. Вам не потрібно вручну керувати фреймуванням SSE або з'єднаннями WebSocket.

Створення кінцевої точки потокового

Ось приємна частина: обробник маршруту Next.js для потокових відповідей ШІ складає близько 30 рядків коду. Функція streamText підключається до вашого постачальника моделі, а toUIMessageStreamResponse() перетворює все в формат, який очікують клієнтські хуки:

typescript
// app/api/chat/route.ts
import { convertToModelMessages, streamText, UIMessage } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

export const maxDuration = 60;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    system: 'Ви - корисний асистент продукту.',
    messages: await convertToModelMessages(messages),
    tools: {
      searchProducts: {
        description: 'Пошук у каталозі продуктів',
        inputSchema: z.object({
          query: z.string(),
          category: z.string().optional(),
        }),
        execute: async ({ query, category }) => {
          // Виклик вашого бекенду Laravel
          const res = await fetch(`${process.env.API_URL}/api/products/search`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query, category }),
          });
          return res.json();
        },
      },
    },
  });

  return result.toUIMessageStreamResponse();
}

Споживач React

На стороні клієнта хук useChat керує станом повідомлень, статусом потокового та викликами інструментів. Властивість parts у кожному повідомленні надає вам типізований доступ до текстових сегментів, викликів інструментів та результатів інструментів — що робить його простим для рендерингу різних UI-обробок для кожного:

typescript
// app/chat/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';

export default function ChatPage() {
  const { messages, sendMessage, status, stop } = useChat({
    transport: new DefaultChatTransport({ api: '/api/chat' }),
  });
  const [input, setInput] = useState('');

  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto">
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((message) => (
          <div key={message.id} className={message.role === 'user' ? 'text-right' : ''}>
            {message.parts.map((part, i) => {
              if (part.type === 'text') return <p key={i}>{part.text}</p>;
              if (part.type === 'tool-invocation') {
                return <ToolStatus key={i} invocation={part} />;
              }
              return null;
            })}
          </div>
        ))}
        {status === 'submitted' && <LoadingIndicator />}
      </div>

      <form onSubmit={(e) => {
        e.preventDefault();
        if (input.trim()) { sendMessage({ text: input }); setInput(''); }
      }}>
        <input value={input} onChange={(e) => setInput(e.target.value)}
          disabled={status !== 'ready'} placeholder="Запитайте щось..." />
        {status === 'streaming'
          ? <button type="button" onClick={() => stop()}>Зупинити</button>
          : <button type="submit" disabled={status !== 'ready'}>Відправити</button>}
      </form>
    </div>
  );
}

Чотири значення статусу повідомляють вам все: submitted (запит надіслано, чекаємо на перший токен), streaming (активно отримуємо), ready (готово, приймаємо новий ввід) та error (щось пішло не так). Цього достатньо, щоб керувати спінерами завантаження, станами відключення та кнопками скасування. Ви ніколи не торкаєтеся до фреймів WebSocket або парсингу SSE безпосередньо.

Створення агентних шаблонів UI

Потоковий текст забезпечує вам половину шляху. Інша половина? Показати користувачам, що насправді робить ШІ за лаштунками. Агентний додаток може шукати в базі даних, викликати зовнішній API, чекати на підтвердження людини, а потім генерувати підсумок. Уся ця послідовність може зайняти 30 секунд або більше. Якщо ваш UI сприймає це як чорну скриньку з спінером завантаження, користувачі швидко втратять довіру. Їм потрібна видимість.

Візуалізація викликів інструментів

Уявіть собі: користувач просить вашого асистента знайти найкращі продукти за продажами в Q4. Модель викликає інструмент searchProducts. Без будь-якого візуального зворотного зв'язку користувач просто бачить... нічого. Мигаючий курсор. Можливо, вони запитують, чи не завис додаток. Невелика картка статусу, яка говорить "Пошук продуктів для бестселерів Q4...", змінює весь досвід. Vercel AI SDK надає це безкоштовно через масив parts, де кожен виклик інструмента з'являється з його назвою, аргументами та поточним станом:

typescript
// components/ToolStatus.tsx
'use client';

type ToolInvocationPart = {
  type: 'tool-invocation';
  toolInvocation: {
    toolName: string;
    state: 'call' | 'result' | 'partial-call';
    args: Record<string, unknown>;
    result?: unknown;
  };
};

export function ToolStatus({ invocation }: { invocation: ToolInvocationPart }) {
  const { toolName, state, args, result } = invocation.toolInvocation;

  const labels: Record<string, string> = {
    searchProducts: 'Пошук продуктів',
    getOrderStatus: 'Перевірка статусу замовлення',
    generateReport: 'Генерація звіту',
  };

  return (
    <div className="border rounded-lg p-3 my-2 bg-gray-50">
      <div className="flex items-center gap-2">
        {state === 'call' && <Spinner size="sm" />}
        {state === 'result' && <CheckIcon className="text-green-600" />}
        <span className="font-medium">
          {labels[toolName] ?? toolName}
        </span>
      </div>

      {state === 'call' && (
        <p className="text-sm text-gray-500 mt-1">
          {toolName === 'searchProducts' && `Шукаю "${args.query}"...`}
          {toolName === 'generateReport' && 'Це може зайняти деякий час...'}
        </p>
      )}

      {state === 'result' && result && (
        <div className="mt-2 text-sm">
          <ToolResultPreview toolName={toolName} result={result} />
        </div>
      )}
    </div>
  );
}

Підтвердження з людиною в циклі

Не кожен виклик інструмента повинен виконуватися автоматично. Уявіть, що модель вирішує надіслати електронний лист від імені вашого користувача, або обробити повернення, або видалити партію записів. Вам абсолютно потрібно, щоб людина натиснула "так" перед цим. AI SDK елегантно вирішує це: визначте інструмент на стороні клієнта без функції execute, і він з'явиться в UI як очікуюча дія замість того, щоб виконуватися негайно. Користувач бачить, що модель хоче зробити, підтверджує або відхиляє, і результат повертається через addToolOutput:

typescript
// Усередині вашого компонента чату
const { messages, sendMessage, addToolOutput } = useChat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
  sendAutomaticallyWhen:
    lastAssistantMessageIsCompleteWithToolCalls,
  onToolCall: ({ toolCall, addToolOutput: add }) => {
    // Авто-виконати безпечні інструменти
    if (toolCall.toolName === 'getLocation') {
      add({ toolCallId: toolCall.toolCallId, result: navigator.geolocation });
    }
    // Небезпечні інструменти рендеряться в UI — без авто-виконання
  },
});

// Рендер підтвердження для чутливих інструментів
{message.parts.map((part) => {
  if (part.type === 'tool-invocation'
    && part.toolInvocation.toolName === 'sendEmail'
    && part.toolInvocation.state === 'call') {
    return (
      <ConfirmationCard
        title="Надіслати цей електронний лист?"
        details={part.toolInvocation.args}
        onConfirm={() => addToolOutput({
          toolCallId: part.toolInvocation.toolCallId,
          result: { confirmed: true },
        })}
        onReject={() => addToolOutput({
          toolCallId: part.toolInvocation.toolCallId,
          result: { confirmed: false, reason: 'Користувач відмовився' },
        })}
      />
    );
  }
})}

Оптимістичні оновлення для контенту, згенерованого ШІ

Коли ШІ складає електронний лист або генерує звіт, не змушуйте користувача чекати на обробку, перш ніж вони зможуть його побачити та редагувати. Рендерте згенерований контент негайно в редагованому стані. Дайте їм можливість змінити формулювання, виправити число, додати абзац. Відправте фінальну версію лише після того, як вони будуть задоволені. Цей маленький шаблон змушує ШІ відчувати себе співпрацівником, а не оракулом, і тихо вирішує проблему довіри. Люди довіряють виходу, який вони можуть редагувати.

Підключення вашого фронтенду Next.js до бекенду ШІ на Laravel

Багато виробничих додатків ШІ розділяють обов'язки: Next.js обробляє фронтенд та потокову оркестрацію, тоді як бекенд Laravel управляє бізнес-логікою, операціями з базою даних та тривалими завданнями ШІ. Це розділення працює добре, але контракт API між ними потребує ретельного проектування.

Проектування API для кінцевих точок ШІ

Кінцеві точки ШІ поводяться інакше, ніж стандартні ресурси REST. Синхронна кінцева точка, яка повертає за 200 мс, не працюватиме, коли операція під капотом включає кілька викликів LLM, які займають 15 секунд. Проектуйте свій API з трьома рівнями:

Шаблон

Випадок використання

Відповідь

Синхронний

Короткі завдання ШІ (<5с) — класифікація, витягування

Стандартний JSON

Потоковий

Генерація тексту, розмова

SSE / часткова передача

Асинхронне завдання

Тривалі завдання — генерація звітів, пакетний аналіз

ID завдання + опитування / веб-хук

Для потокових кінцевих точок Laravel може проксювати потік постачальника LLM безпосередньо до фронтенду Next.js, використовуючи кодування часткової передачі. Для асинхронних завдань використовуйте систему черг Laravel і відкрийте кінцеву точку статусу, яку фронтенд опитує з поступовими інтервалами.

Аутентифікація: NextAuth + Laravel Sanctum

Найчистіший шаблон поєднує NextAuth (або Auth.js) на стороні Next.js з Laravel Sanctum на бекенді. NextAuth обробляє потік OAuth та управління сесіями. Після успішної аутентифікації видайте токен Sanctum, який код на стороні сервера Next.js включає в кожен запит до Laravel. Це зберігає стан аутентифікації узгодженим між обома шарами без спільного зберігання сесій.

Обробка помилок для специфічних для ШІ збоїв

Кінцеві точки ШІ зазнають збоїв у способах, які традиційні API не роблять. Ліміти швидкості від постачальників моделей з'являються без попередження. Помилки довжини контексту з'являються, коли історія розмови стає занадто довгою. Модель може повернути контент, який активує фільтри безпеки. Ваша обробка помилок повинна враховувати все це:

  • Ліміти швидкості (429): Ставте запит у чергу та повторіть з експоненційним відступом. Показуйте користувачам повідомлення "високий попит" замість сирої помилки.

  • Перевищено довжину контексту: Автоматично обріжте старі повідомлення з розмови. convertToModelMessages Vercel AI SDK може допомогти в управлінні цим.

  • Тригери фільтрів контенту: Поверніть структуровану помилку, яку фронтенд рендерить як м'який редирект, а не трасування стеку.

  • Тайм-аут на тривалих генераціях: Використовуйте експорт maxDuration в обробниках маршрутів (до 300с на Vercel Pro) і реалізуйте відновлення тайм-ауту на стороні клієнта.

Куди це йде далі

Рік тому "інтеграція ШІ" означала додавання вікна чату десь у вашому додатку. Це вже не те, про що ми говоримо. Уся модель взаємодії фронтенду змінюється: потокова передача замінює запит-відповідь як стандарт, виклики інструментів стають примітивом UI, навколо якого ви проектуєте, а агенти з'являються як законні користувачі поряд з людьми. Якщо ваш додаток Next.js добре справляється з усіма трьома, ви попереду більшості команд, які сьогодні займаються розробкою.

Інструменти наздогнали амбіції. Vercel AI SDK охоплює виклики інструментів, підтримку MCP, генеративний UI та робочі процеси агентів. Next.js 15 надає вам обробники маршрутів, компоненти сервера React та межі затримки, які роблять потокове відчуття рідним. Прийняття MCP прискорюється, і чим раніше ви відкриєте сумісні кінцеві точки, тим більше трафіку агентів ви зможете захопити, оскільки ця екосистема зростає.

З чого почати? Спочатку налаштуйте потокове, з належною візуалізацією інструментів, щоб користувачі могли бачити, що робить ШІ. Додайте потоки підтвердження для всього чутливого. Коли це буде виглядати надійно, зверніть увагу на MCP, щоб відкрити ваш додаток для агентних користувачів. Кожен шар будується на попередньому.

Звичайно, знання, як будувати ці шаблони, — це лише половина головоломки. Знати, які з них пріоритетні, як виміряти, чи працюють вони, і коли відправити, а коли ітерувати? Це стратегія продукту. Курс AI Product Manager на SkillHub глибоко розглядає саме ці рішення, і варто звернути на нього увагу, якщо ви одночасно приймаєте технічні та продуктові рішення.

Kirill Latish
Kirill Latish
LinkedIn
Share