Kurs Next.js · Moduł 9: Integracje i zaawansowane funkcje

after() API - Wykonywanie kodu po odpowiedzi

8 min czytania
W tej lekcji10

Użytkownik Metropolii Quantum 2150 kupuje bilet na lot orbitalny i czeka. Zamówienie zapisało się w 50 ms, ale serwer wciąż wysyła email, aktualizuje magazyn i loguje zdarzenie do systemu audytu, więc ekran potwierdzenia pojawia się po dwóch sekundach. Cypher, strażnik wydajności Metropolii, zna na to lekarstwo: API after() - funkcję pozwalającą wykonać kod po wysłaniu odpowiedzi do użytkownika. To idealne rozwiązanie dla zadań takich jak logowanie, analityka czy cleanup.

Problem: Blokujące operacje poboczne

Wyobraź sobie typowy scenariusz - użytkownik odwiedza stronę produktu, a ty chcesz:

  1. Zwrócić dane produktu (kluczowe)
  2. Zalogować wizytę (poboczne)
  3. Zaktualizować statystyki (poboczne)
  4. Wysłać event do analytics (poboczne)

Bez after() każde await wydłuża czas odpowiedzi, choć użytkownik potrzebuje tylko danych produktu. Tak wygląda Route Handler, w którym wszystko blokuje odpowiedź:

1// Złe podejście - wszystko blokuje odpowiedź
2export async function GET(request: Request) {
3  const product = await getProduct(id);
4
5  // Te operacje opóźniają odpowiedź!
6  await logVisit(id);
7  await updateStats(id);
8  await sendAnalytics('product_view', { id });
9
10  return Response.json(product);
11}

Użytkownik czeka na sumę czasów wszystkich czterech operacji, a trzy z nich w ogóle go nie dotyczą.

Rozwiązanie: after() API

Funkcja after() z next/server przyjmuje callback i planuje jego wykonanie PO zakończeniu odpowiedzi. Kod poboczny przenosimy do środka callbacku:

1import { after } from 'next/server';
2
3export async function GET(request: Request) {
4  const product = await getProduct(id);
5
6  // Zaplanuj wykonanie po odpowiedzi
7  after(async () => {
8    await logVisit(id);
9    await updateStats(id);
10    await sendAnalytics('product_view', { id });
11  });
12
13  // Odpowiedź wysyłana natychmiast!
14  return Response.json(product);
15}

Odpowiedź wychodzi natychmiast po pobraniu produktu, a logowanie i statystyki wykonują się chwilę później. Kolejność operacji w callbacku się nie zmieniła, zmienił się tylko moment ich startu.

Włączenie after() API

W Next.js 15.0 (od wersji RC do wydania stabilnego) funkcja nazywała się unstable_after i wymagała flagi eksperymentalnej:

1// next.config.js - tylko Next.js 15.0 (unstable_after), od 15.1 zbędne
2module.exports = {
3  experimental: {
4    after: true,
5  },
6};

Od Next.js 15.1 after() jest stabilne, więc ta flaga nie jest już potrzebna i w Next.js 16 po prostu importujesz funkcję. Warto też wiedzieć, że after() nie jest API czasu żądania: jego użycie nie robi z trasy strony dynamicznej, a na stronie statycznej callback wykona się podczas budowania lub rewalidacji.

Użycie w różnych kontekstach

after() działa w Server Components (także w generateMetadata), Server Actions, Route Handlers i w Proxy (dawnym Middleware).

W Route Handlers

Route Handler to plik route.ts eksportujący funkcję nazwaną metodą HTTP, np. export async function POST(request: Request), która czyta ciało przez await request.json(). Tak wygląda tworzenie zamówienia z efektami pobocznymi:

1// app/api/orders/route.ts
2import { after } from 'next/server';
3import { sendEmail, updateInventory, notifyWarehouse } from '@/lib/services';
4
5export async function POST(request: Request) {
6  const orderData = await request.json();
7
8  // Kluczowa operacja - tworzenie zamówienia
9  const order = await createOrder(orderData);
10
11  // Operacje poboczne - wykonane po odpowiedzi
12  after(async () => {
13    // Email potwierdzający
14    await sendEmail({
15      to: orderData.email,
16      subject: 'Potwierdzenie zamówienia',
17      template: 'order-confirmation',
18      data: order,
19    });
20
21    // Aktualizacja stanów magazynowych
22    await updateInventory(order.items);
23
24    // Powiadomienie magazynu
25    await notifyWarehouse(order);
26
27    // Logowanie do systemu audytu
28    await auditLog('order_created', { orderId: order.id });
29  });
30
31  // Odpowiedź natychmiastowa
32  return Response.json({
33    success: true,
34    orderId: order.id
35  });
36}

Klient dostaje orderId od razu, a email, magazyn i audyt obsługuje się w tle. W Route Handlers możesz nawet wywołać cookies() i headers() wewnątrz callbacku after().

W Server Components

W komponencie serwerowym after() śledzi wyświetlenie strony po jej wyrenderowaniu:

1// app/products/[id]/page.tsx
2import { after } from 'next/server';
3import { trackPageView } from '@/lib/analytics';
4
5export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
6  const product = await getProduct((await params).id);
7
8  // Tracking po renderowaniu strony
9  after(async () => {
10    await trackPageView({
11      page: 'product',
12      productId: (await params).id,
13      productName: product.name,
14      category: product.category,
15    });
16  });
17
18  return (
19    <div className="product-page">
20      <h1>{product.name}</h1>
21      <p>{product.description}</p>
22      <span className="price">{product.price} zł</span>
23    </div>
24  );
25}

Uwaga na ograniczenie: w Server Components wewnątrz after() nie wolno wołać cookies() ani headers(), bo to rzuci błąd. Takie dane odczytaj wcześniej, w ciele komponentu, i przekaż do callbacku przez domknięcie.

W Server Actions

Server Action najpierw wykonuje operację kluczową i rewaliduje ścieżki, a resztę odkłada:

1// app/actions/checkout.ts
2'use server';
3
4import { after } from 'next/server';
5import { revalidatePath } from 'next/cache';
6
7export async function completeCheckout(formData: FormData) {
8  const cartId = formData.get('cartId') as string;
9
10  // Kluczowa operacja
11  const order = await processPayment(cartId);
12
13  if (!order.success) {
14    return { error: 'Płatność nie powiodła się' };
15  }
16
17  // Rewalidacja ścieżek
18  revalidatePath('/cart');
19  revalidatePath('/orders');
20
21  // Operacje poboczne po odpowiedzi
22  after(async () => {
23    // Czyszczenie koszyka w tle
24    await clearCart(cartId);
25
26    // Generowanie faktury PDF
27    await generateInvoice(order.id);
28
29    // Integracje zewnętrzne
30    await syncWithERP(order);
31    await notifyShipping(order);
32
33    // Aktualizacja programu lojalnościowego
34    await updateLoyaltyPoints(order.userId, order.total);
35  });
36
37  return { success: true, orderId: order.id };
38}

Użytkownik widzi potwierdzenie płatności, zanim wygeneruje się faktura PDF. Rewalidacja zostaje przed after(), bo od niej zależy, co użytkownik zobaczy.

Wiele wywołań after()

Możesz użyć after() wielokrotnie - wszystkie callbacki zostaną wykonane:

1import { after } from 'next/server';
2
3export async function POST(request: Request) {
4  const data = await request.json();
5  const result = await processData(data);
6
7  // Logowanie
8  after(async () => {
9    await logger.info('Data processed', { id: result.id });
10  });
11
12  // Analytics
13  after(async () => {
14    await analytics.track('data_processed', {
15      processingTime: result.duration,
16      dataSize: data.length,
17    });
18  });
19
20  // Cleanup
21  after(async () => {
22    await cleanupTempFiles(result.tempPath);
23  });
24
25  return Response.json(result);
26}

Osobne callbacki porządkują kod według odpowiedzialności: logowanie, analityka, sprzątanie. Wywołania after() można też zagnieżdżać, np. w funkcjach pomocniczych.

Obsługa błędów w after()

Błędy w after() nie wpływają na odpowiedź, bo ta już poszła do klienta, ale powinny być obsługiwane:

1import { after } from 'next/server';
2
3export async function GET(request: Request) {
4  const data = await fetchData();
5
6  after(async () => {
7    try {
8      await riskyOperation();
9    } catch (error) {
10      // Loguj błąd, ale nie przerywaj
11      console.error('After callback failed:', error);
12      await errorReporting.capture(error);
13    }
14  });
15
16  return Response.json(data);
17}

Blok try/catch przechwytuje błąd i wysyła go do systemu raportowania, zamiast zostawić go bez śladu. Callback wykona się nawet wtedy, gdy odpowiedź zakończyła się błędem albo wywołaniem notFound() lub redirect().

Praktyczny przykład - System notyfikacji

Na koniec pełny Route Handler polubienia posta. Autoryzacja i zapis są kluczowe, a powiadomienia, statystyki i ranking mogą poczekać:

1// app/api/posts/[id]/like/route.ts
2import { after } from 'next/server';
3import { getSession } from '@/lib/auth';
4
5export async function POST(
6  request: Request,
7  { params }: { params: Promise<{ id: string }> }
8) {
9  const session = await getSession();
10  if (!session) {
11    return Response.json({ error: 'Unauthorized' }, { status: 401 });
12  }
13
14  // Kluczowa operacja - dodanie like'a
15  const like = await addLike((await params).id, session.userId);
16
17  // Wszystkie powiadomienia i side effects po odpowiedzi
18  after(async () => {
19    const post = await getPost((await params).id);
20
21    // Powiadomienie autora posta
22    if (post.authorId !== session.userId) {
23      await createNotification({
24        userId: post.authorId,
25        type: 'like',
26        message: `${session.user.name} polubił Twój post`,
27        link: `/posts/${(await params).id}`,
28      });
29
30      // Push notification
31      await sendPushNotification(post.authorId, {
32        title: 'Nowy like!',
33        body: `${session.user.name} polubił Twój post`,
34      });
35    }
36
37    // Aktualizacja statystyk
38    await incrementPostStats((await params).id, 'likes');
39
40    // Aktualizacja feed algorytmu
41    await updateFeedRanking((await params).id, 'engagement');
42  });
43
44  return Response.json({ liked: true, likeCount: like.count });
45}

Sprawdzenie sesji zostaje przed odpowiedzią, bo od niego zależy kod 401. Wszystko, co tylko informuje innych, trafia do after().

after() w specjalnych plikach tras

Ten sam model dotyczy specjalnych plików w katalogu app/, które Next.js traktuje jak Route Handlers: app/sitemap.js, app/robots.js, app/manifest.js i app/opengraph-image.js. Każdy z nich eksportuje funkcję domyślną, np. export default function sitemap():

1// app/sitemap.ts
2import type { MetadataRoute } from 'next';
3
4export default function sitemap(): MetadataRoute.Sitemap {
5  return [{ url: 'https://quantum.example.com/', lastModified: new Date() }];
6}

Taki plik generuje /sitemap.xml bez ręcznego pisania XML-a. Podobny schemat ma webhook z zewnętrznego serwisu: endpoint, weryfikacja podpisu, przetworzenie zdarzenia, odpowiedź 200 OK. Ciężkie przetwarzanie możesz przenieść do after(), żeby szybko potwierdzić odbiór.

Kiedy używać after()?

Idealne przypadki użycia:

  • Logowanie i audyt - zapisywanie działań użytkowników
  • Analytics - śledzenie eventów i metryk
  • Powiadomienia - wysyłanie emaili, push notifications
  • Cleanup - czyszczenie plików tymczasowych
  • Synchronizacja - aktualizacja zewnętrznych systemów
  • Generowanie raportów - tworzenie plików w tle

Nie używaj dla:

  • Walidacji - musi być przed odpowiedzią
  • Autoryzacji - musi być przed odpowiedzią
  • Danych potrzebnych w odpowiedzi - oczywiste!
  • Operacji wymagających potwierdzenia - użytkownik nie zobaczy wyniku

Pamiętaj o limicie czasu: after() działa najwyżej tak długo, jak pozwala domyślny lub ustawiony w maxDuration limit trasy. Na platformach serverless Next.js korzysta z mechanizmu waitUntil, a eksport statyczny nie obsługuje after() wcale.

Podsumowanie

API after() w Next.js to potężne narzędzie optymalizacji:

  • Szybsze odpowiedzi - operacje poboczne nie blokują użytkownika
  • Lepsza UX - użytkownik widzi wynik natychmiast
  • Czystszy kod - wyraźna separacja operacji kluczowych i pobocznych
  • Niezawodność - błędy w after() nie psują odpowiedzi

Moja rada: przy każdym await w handlerze zapytaj, czy użytkownik naprawdę musi na nie czekać. Jeśli nie, przenieś je do after(). W następnej lekcji poznasz dyrektywę use cache, która przyspiesza z kolei samo pobieranie danych.

Zapamiętaj: w Metropolii Quantum odpowiedź leci do użytkownika z prędkością światła, a after() sprząta za nią w ciszy.

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Do czego służy after() API w Next.js 16?

  2. 2. Który katalog w Next.js zawiera statyczne zasoby dostępne publicznie?

Zadania praktyczne w grze

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: app/ → sitemap.js → export default

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: Submit PR → Review code → Address feedba

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: app/ → robots.js → export default

  • Klikanie w kolejności

    Ułóż składnię route handlera API w Next.js App Router

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: Bump version → Update changelog → Tag re

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: app/ → opengraph-image.js → export defau

  • Układanie w pionie

    Ułóż kroki integracji płatności Stripe w Next.js

  • Układanie w pionie

    Ułóż ścieżkę i eksport pliku manifest.js

  • Układanie w pionie

    Ułóż kroki obsługi webhook z zewnętrznego serwisu

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: Server Component fetch → Client Componen

Przydatne artykuły