Cours Next.js · Module 9: Intégrations et fonctions avancées
API after() - Exécuter du code après la réponse
Dans cette leçon10
Un habitant de la Métropole Quantum 2150 achète un billet pour un vol orbital et attend. La commande a été enregistrée en 50 ms, mais le serveur envoie encore un email, met à jour le stock et journalise l'événement dans le système d'audit, donc l'écran de confirmation apparaît au bout de deux secondes. Cypher, le gardien des performances de la Métropole, connaît le remède : l'API after() - une fonction qui permet d'exécuter du code après l'envoi de la réponse à l'utilisateur. C'est la solution idéale pour des tâches comme la journalisation, l'analytics ou le nettoyage.
Problème : les opérations secondaires bloquantes
Imagine un scénario typique - un utilisateur visite une page produit, et tu veux :
- Renvoyer les données du produit (essentiel)
- Journaliser la visite (secondaire)
- Mettre à jour les statistiques (secondaire)
- Envoyer un événement à l'analytics (secondaire)
Sans after(), chaque await allonge le temps de réponse, alors que l'utilisateur n'a besoin que des données du produit. Voici un Route Handler dans lequel tout bloque la réponse :
1// Mauvaise approche - tout bloque la réponse
2export async function GET(request: Request) {
3 const product = await getProduct(id);
4
5 // Ces opérations retardent la réponse !
6 await logVisit(id);
7 await updateStats(id);
8 await sendAnalytics('product_view', { id });
9
10 return Response.json(product);
11}L'utilisateur attend la somme des quatre opérations, alors que trois d'entre elles ne le concernent pas du tout.
Solution : l'API after()
La fonction after() de next/server reçoit un callback et planifie son exécution APRÈS la fin de la réponse. On déplace le code secondaire dans le callback :
1import { after } from 'next/server';
2
3export async function GET(request: Request) {
4 const product = await getProduct(id);
5
6 // Planifie l'exécution après la réponse
7 after(async () => {
8 await logVisit(id);
9 await updateStats(id);
10 await sendAnalytics('product_view', { id });
11 });
12
13 // La réponse est envoyée immédiatement !
14 return Response.json(product);
15}La réponse part juste après la récupération du produit, et la journalisation et les statistiques s'exécutent un instant plus tard. L'ordre des opérations dans le callback n'a pas changé, seul leur moment de démarrage a changé.
Activer l'API after()
Dans Next.js 15.0 (de la version RC à la version stable), la fonction s'appelait unstable_after et exigeait un flag expérimental :
1// next.config.js - uniquement Next.js 15.0 (unstable_after), inutile depuis la 15.1
2module.exports = {
3 experimental: {
4 after: true,
5 },
6};Depuis Next.js 15.1, after() est stable, donc ce flag n'est plus nécessaire et dans Next.js 16 tu importes simplement la fonction. Bon à savoir aussi : after() n'est pas une API liée à la requête, son utilisation ne rend pas la route dynamique, et sur une page statique le callback s'exécute au build ou lors de la revalidation.
Utilisation dans différents contextes
after() fonctionne dans les Server Components (y compris generateMetadata), les Server Actions, les Route Handlers et dans Proxy (l'ancien Middleware).
Dans les Route Handlers
Un Route Handler est un fichier route.ts qui exporte une fonction portant le nom d'une méthode HTTP, par exemple export async function POST(request: Request), qui lit le corps avec await request.json(). Voici la création d'une commande avec des effets de bord :
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 // Opération essentielle - crée la commande
9 const order = await createOrder(orderData);
10
11 // Effets de bord - exécutés après la réponse
12 after(async () => {
13 // Email de confirmation
14 await sendEmail({
15 to: orderData.email,
16 subject: 'Confirmation de commande',
17 template: 'order-confirmation',
18 data: order,
19 });
20
21 // Met à jour le stock
22 await updateInventory(order.items);
23
24 // Notifie l'entrepôt
25 await notifyWarehouse(order);
26
27 // Journal d'audit
28 await auditLog('order_created', { orderId: order.id });
29 });
30
31 // Réponse immédiate
32 return Response.json({
33 success: true,
34 orderId: order.id
35 });
36}Le client reçoit l'orderId immédiatement, tandis que l'email, le stock et l'audit sont traités en arrière-plan. Dans les Route Handlers, tu peux même appeler cookies() et headers() dans le callback de after().
Dans les Server Components
Dans un composant serveur, after() suit l'affichage d'une page après son rendu :
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 après le rendu de la page
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} €</span>
23 </div>
24 );
25}Attention à une limite : dans les Server Components, tu ne dois pas appeler cookies() ni headers() dans after(), car cela lève une erreur. Lis ces données avant, dans le corps du composant, et transmets-les au callback par une closure.
Dans les Server Actions
Une Server Action effectue d'abord l'opération essentielle et revalide les chemins, puis reporte le reste :
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 // Opération essentielle
11 const order = await processPayment(cartId);
12
13 if (!order.success) {
14 return { error: 'Le paiement a échoué' };
15 }
16
17 // Revalide les chemins
18 revalidatePath('/cart');
19 revalidatePath('/orders');
20
21 // Effets de bord après la réponse
22 after(async () => {
23 // Vide le panier en arrière-plan
24 await clearCart(cartId);
25
26 // Génère la facture PDF
27 await generateInvoice(order.id);
28
29 // Intégrations externes
30 await syncWithERP(order);
31 await notifyShipping(order);
32
33 // Met à jour le programme de fidélité
34 await updateLoyaltyPoints(order.userId, order.total);
35 });
36
37 return { success: true, orderId: order.id };
38}L'utilisateur voit la confirmation du paiement avant que la facture PDF ne soit générée. La revalidation reste avant after(), car ce que voit l'utilisateur en dépend.
Plusieurs appels à after()
Tu peux utiliser after() plusieurs fois - tous les callbacks seront exécutés :
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 // Journalisation
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 // Nettoyage
21 after(async () => {
22 await cleanupTempFiles(result.tempPath);
23 });
24
25 return Response.json(result);
26}Des callbacks séparés organisent le code par responsabilité : journalisation, analytics, nettoyage. Les appels à after() peuvent aussi être imbriqués, par exemple dans des fonctions utilitaires.
Gestion des erreurs dans after()
Les erreurs dans after() n'affectent pas la réponse, puisqu'elle est déjà partie vers le client, mais il faut quand même les gérer :
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 // Journalise l'erreur sans interrompre
11 console.error('After callback failed:', error);
12 await errorReporting.capture(error);
13 }
14 });
15
16 return Response.json(data);
17}Le bloc try/catch intercepte l'erreur et l'envoie au système de rapport au lieu de la laisser disparaître. Le callback s'exécute même quand la réponse s'est terminée par une erreur ou par un appel à notFound() ou redirect().
Exemple pratique - Système de notifications
Pour finir, un Route Handler complet pour aimer un post. L'autorisation et l'enregistrement sont essentiels, tandis que les notifications, les statistiques et le classement peuvent attendre :
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 // Opération essentielle - ajoute le like
15 const like = await addLike((await params).id, session.userId);
16
17 // Toutes les notifications et effets de bord après la réponse
18 after(async () => {
19 const post = await getPost((await params).id);
20
21 // Notifie l'auteur du post
22 if (post.authorId !== session.userId) {
23 await createNotification({
24 userId: post.authorId,
25 type: 'like',
26 message: `${session.user.name} a aimé ton post`,
27 link: `/posts/${(await params).id}`,
28 });
29
30 // Notification push
31 await sendPushNotification(post.authorId, {
32 title: 'Nouveau like !',
33 body: `${session.user.name} a aimé ton post`,
34 });
35 }
36
37 // Met à jour les statistiques
38 await incrementPostStats((await params).id, 'likes');
39
40 // Met à jour l'algorithme du feed
41 await updateFeedRanking((await params).id, 'engagement');
42 });
43
44 return Response.json({ liked: true, likeCount: like.count });
45}La vérification de la session reste avant la réponse, car le code 401 en dépend. Tout ce qui ne fait qu'informer les autres va dans after().
after() dans les fichiers de routes spéciaux
Le même modèle s'applique aux fichiers spéciaux du dossier app/ que Next.js traite comme des Route Handlers : app/sitemap.js, app/robots.js, app/manifest.js et app/opengraph-image.js. Chacun exporte une fonction par défaut, par exemple 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}Ce fichier génère /sitemap.xml sans écrire de XML à la main. Un webhook provenant d'un service externe suit un schéma similaire : endpoint, vérification de la signature, traitement de l'événement, réponse 200 OK. Tu peux déplacer le traitement lourd dans after() pour accuser réception rapidement.
Quand utiliser after() ?
Cas d'usage idéaux :
- Journalisation et audit - enregistrer les actions des utilisateurs
- Analytics - suivre les événements et les métriques
- Notifications - envoyer des emails, des notifications push
- Nettoyage - supprimer les fichiers temporaires
- Synchronisation - mettre à jour des systèmes externes
- Génération de rapports - créer des fichiers en arrière-plan
Ne l'utilise pas pour :
- La validation - elle doit avoir lieu avant la réponse
- L'autorisation - elle doit avoir lieu avant la réponse
- Les données nécessaires dans la réponse - évidemment !
- Les opérations qui exigent une confirmation - l'utilisateur ne verra pas le résultat
Pense à la limite de temps : after() s'exécute au maximum aussi longtemps que le permet la limite par défaut de la route ou celle définie avec maxDuration. Sur les plateformes serverless, Next.js s'appuie sur le mécanisme waitUntil, et l'export statique ne prend pas du tout en charge after().
Résumé
L'API after() de Next.js est un puissant outil d'optimisation :
- Réponses plus rapides - les opérations secondaires ne bloquent pas l'utilisateur
- Meilleure UX - l'utilisateur voit le résultat immédiatement
- Code plus propre - une séparation claire entre opérations essentielles et secondaires
- Fiabilité - les erreurs dans after() ne cassent pas la réponse
Mon conseil : à chaque await dans un handler, demande-toi si l'utilisateur doit vraiment l'attendre. Sinon, déplace-le dans after(). Dans la leçon suivante, tu découvriras la directive use cache, qui accélère quant à elle la récupération des données.
Retiens : dans la Métropole Quantum, la réponse file vers l'utilisateur à la vitesse de la lumière, et after() fait le ménage derrière elle en silence.
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. À quoi sert l'API after() dans Next.js 16 ?
2. Quel répertoire de Next.js contient les fichiers statiques accessibles publiquement ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Tri vertical
Remets les éléments dans le bon ordre : app/ → sitemap.js → export default
- Tri vertical
Remets les éléments dans le bon ordre : Submit PR → Review code → Address feedback
- Tri vertical
Remets les éléments dans le bon ordre : app/ → robots.js → export default
- Tri vertical
Remets les éléments dans le bon ordre : Bump version → Update changelog → Tag release
- Tri vertical
Remets les éléments dans le bon ordre : app/ → opengraph-image.js → export default
- Tri vertical
Remets dans l'ordre les étapes de l'intégration des paiements Stripe dans Next.js
- Tri vertical
Remets dans l'ordre le chemin et l'export du fichier manifest.js
- Tri vertical
Remets dans l'ordre les étapes du traitement d'un webhook provenant d'un service externe
- Clique dans l'ordre
Remets dans l'ordre la syntaxe d'un route handler d'API dans l'App Router de Next.js
- Tri vertical
Remets les éléments dans le bon ordre : Server Component fetch → Client Component mount
- Tri vertical
Remets les éléments dans le bon ordre : export default → function Page() → {
- Tri vertical
Remets dans l'ordre les étapes de l'intégration d'un headless CMS avec Next.js