← Όλα τα άρθρα

i18n middleware στο Next.js: βρόχοι ανακατεύθυνσης

Ανίχνευση locale και i18n ανακατευθύνσεις — εδώ ζουν τα περισσότερα bugs. Περιορισμοί edge runtime, matcher config, testing χωρίς deploy.

i18n middleware στο Next.js: βρόχοι ανακατεύθυνσης

Το edge middleware του Vercel τρέχει πριν από οποιαδήποτε σελίδα αποδίδεται, σε κάθε αίτηση, στο CDN edge. Για έναν διεθνοποιημένο ιστότοπο Next.js, το middleware είναι εκεί που συμβαίνουν η ανίχνευση locale, οι ανακατευθύνσεις και η επανεγγραφή διαδρομών. Είναι επίσης εκεί που ζει ένας εκπληκτικά μεγάλος αριθμός bugs, επειδή το middleware τρέχει εκτός React και εκτός Node.js — είναι ένα περιορισμένο περιβάλλον με τους δικούς του περιορισμούς.

Εδώ είναι τι έχω συναντήσει κατά την κατασκευή και αποσφαλμάτωση middleware i18n στο Vercel.

Οι δύο δουλειές που κάνει το middleware για i18n

Όταν ένας επισκέπτης χτυπά το klawsfx.com/ χωρίς πρόθεμα locale, κάτι πρέπει να αποφασίσει αν θα τον ανακατευθύνει στο /en ή /el. Αυτή είναι η πρώτη δουλειά του middleware: ανίχνευση locale.

Όταν ένας επισκέπτης χτυπά το /services και δεν υπάρχει τέτοια διαδρομή — μόνο /en/services και /el/services — το middleware πρέπει να επανεγγράψει τη διαδρομή διαφανώς. Αυτή είναι η δεύτερη δουλειά: επανεγγραφή διαδρομής.

Το createMiddleware του next-intl χειρίζεται και τα δύο. Το κλειδί είναι να κατανοήσεις τι χρησιμοποιεί για ανίχνευση locale και με ποια σειρά:

  1. Υπάρχον cookie — αν ο χρήστης έχει επισκεφθεί ξανά, το locale αποθηκεύεται σε cookie και γίνεται σεβαστό.
  2. Header Accept-Language — ο browser στέλνει την προτιμώμενη γλώσσα· το middleware την αντιστοιχεί σε υποστηριζόμενο locale.
  3. Προεπιλεγμένο locale — αν κανένα δεν ταιριάζει, επιστροφή στο ρυθμισμένο προεπιλεγμένο.

Η ρύθμιση matcher είναι η πιο συνηθισμένη πηγή bugs

Το middleware τρέχει σε αντιστοιχισμένες διαδρομές. Η εξαγωγή config.matcher στο middleware.ts ορίζει ποιες διαδρομές ενεργοποιούν το middleware. Αν το matcher είναι πολύ ευρύ, το middleware τρέχει σε API routes, στατικά assets και εσωτερικά _next. Αν είναι πολύ στενό, οι ανακατευθύνσεις locale δεν ενεργοποιούνται.

Ο προτεινόμενος matcher του next-intl αποκλείει ρητά τα εσωτερικά:

export const config = {
  matcher: [
    '/((?!_next|_vercel|.*\\..*).*)',
  ],
};

Αν τρέχεις επίσης middleware ταυτοποίησης (έλεγχος cookie συνεδρίας πριν εξυπηρετήσεις admin routes), συνδύασε τη λογική μέσα σε μία μόνο συνάρτηση middleware — μην προσπαθείς να συνθέσεις δύο ξεχωριστά αρχεία middleware. Το Next.js υποστηρίζει μόνο μία εξαγωγή middleware.

// middleware.ts
const intlMiddleware = createIntlMiddleware(routing);

export default function middleware(request: NextRequest) {
  const isAdminRoute = request.nextUrl.pathname.startsWith('/admin');

  if (isAdminRoute) {
    const sessionCookie = request.cookies.get('session');
    if (!sessionCookie) {
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }

  return intlMiddleware(request);
}

Ο περιορισμός εισαγωγής του edge runtime

Το middleware τρέχει στο Vercel Edge Runtime, όχι στο τυπικό Node.js runtime. Το edge runtime είναι περιορισμένο περιβάλλον — υποστηρίζει Web APIs αλλά όχι την πλήρη επιφάνεια API του Node.js. Συγκεκριμένα:

Για επαλήθευση JWT στο middleware, χρησιμοποίησε τη βιβλιοθήκη jose αντί για jsonwebtoken:

import { jwtVerify } from 'jose';

const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);

Βρόχοι ανακατεύθυνσης από λανθασμένη διαχείριση locale

Το πιο αποπροσανατολιστικό bug middleware είναι ένας βρόχος ανακατεύθυνσης. Ο browser χτυπά ένα URL, λαμβάνει 307, το ακολουθεί, λαμβάνει άλλο 307, και τελικά το Chrome εμφανίζει ERR_TOO_MANY_REDIRECTS.

Αυτό σχεδόν πάντα σημαίνει ότι το middleware ανακατευθύνει ένα URL στο οποίο ήδη ανακατέυθυνε. Συνηθισμένες αιτίες:

Η διάγνωση είναι να καταγράψεις το request.nextUrl.pathname και τον προορισμό απόκρισης στην αρχή του middleware, μετά να ελέγξεις τα αρχεία καταγραφής συναρτήσεων του Vercel.

Cookie locale σε Vercel preview deployments

Το next-intl ορίζει ένα cookie locale για να διατηρεί την επιλογή γλώσσας του χρήστη. Από προεπιλογή το cookie δεν έχει ρητό domain, που σημαίνει ότι είναι σκοποθετημένο στον ακριβή hostname — καλό για παραγωγή, αλλά πρόβλημα για Vercel preview deployments.

Τα preview deployments λαμβάνουν URLs όπως klawsfx-git-feature-xyz.vercel.app. Αυτό δεν χρειάζεται διόρθωση για παραγωγή — είναι αναμενόμενη συμπεριφορά. Αλλά αξίζει να το γνωρίζεις όταν αποσφαλματώνεις ζητήματα locale σε URL preview.

Δοκιμή middleware χωρίς ανάπτυξη

Το CLI του Vercel (vercel dev) τρέχει μια πιο κοντινή προσομοίωση του edge περιβάλλοντος από ό,τι το next dev. Για αποσφαλμάτωση ειδικά middleware, χρησιμοποιώ vercel dev τοπικά και ελέγχω τα αρχεία καταγραφής συναρτήσεων εκεί πριν κάνω push.

Για λογική ανακατεύθυνσης και επανεγγραφής, η γραφή ενός τεστ που καλεί απευθείας τη συνάρτηση middleware με mock NextRequest είναι πιο γρήγορη:

import { describe, it, expect } from 'vitest';
import middleware from '../middleware';
import { NextRequest } from 'next/server';

describe('i18n middleware', () => {
  it('ανακατευθύνει τη ρίζα στο προεπιλεγμένο locale', async () => {
    const request = new NextRequest('http://localhost:3000/');
    const response = await middleware(request);
    expect(response.status).toBe(307);
    expect(response.headers.get('location')).toContain('/en');
  });
});

Το mock δεν καλύπτει το πλήρες edge runtime, αλλά εντοπίζει τα περισσότερα σφάλματα λογικής ανακατεύθυνσης και επανεγγραφής χωρίς κύκλο ανάπτυξης.