Kurs Next.js · Moduł 9: Integracje i zaawansowane funkcje
after() API - Wykonywanie kodu po odpowiedzi
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:
- Zwrócić dane produktu (kluczowe)
- Zalogować wizytę (poboczne)
- Zaktualizować statystyki (poboczne)
- 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. Do czego służy after() API w Next.js 16?
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