Funkcje middleware

Sygnatura taka sama jak w natywnym middleware Next.js, więc gotowe funkcje z zewnętrznych pakietów wchodzą bez adaptera. Nagłówki i obiekt event.

Funkcja middleware w NEMO ma dokładnie tę samą sygnaturę co natywne middleware w Next.js. Dzięki temu gotowe funkcje z zewnętrznych pakietów wchodzą do łańcucha bez żadnego adaptera, a Ty piszesz własne tak jak dotąd.

Sygnatura

Zgodność jest pełna w obie strony. NEMO dokłada tylko kilka pól i metod do drugiego argumentu, czyli do event.

_middleware.ts
import { NextMiddleware } from "@zanreal/nemo";

const example: NextMiddleware = async (request, event) => {
  // function body
};

Podpowiedź

Najedź kursorem na fragment sygnatury albo na nazwę pola, żeby zobaczyć pełne typy.

Argumenty

Argument:  request

Typ: NextRequest

Żądanie użytkownika w postaci, w jakiej weszło do łańcucha. Kolejne funkcje mogą je zmieniać.

Ciasteczka odbiegają od pierwotnego żądania tylko wtedy, gdy wcześniejsze ogniwo przekazało dalej własną odpowiedź.

Przekazywanie nagłówków

Nagłówki dołożone przez NextResponse.next() z opcją request docierają zarówno do kolejnych ogniw łańcucha, jak i do komponentów strony:

const middleware: NextMiddleware = async (request) => {
  const newHeaders = new Headers(request.headers);
  newHeaders.set("x-custom-header", "value");

  return NextResponse.next({
    request: {
      headers: newHeaders,
    },
  });
};

Jak to działa:

  • nagłówki ustawione przez NextResponse.next({ request: { headers } }) widzą kolejne funkcje w łańcuchu
  • w komponentach strony sięgasz po nie funkcją headers() z Next.js
  • przechodzą przez cały łańcuch aż do renderowania strony

Przykład z dwiema funkcjami:

const addLocale: NextMiddleware = async (request) => {
  const locale = detectLocale(request);
  const newHeaders = new Headers(request.headers);
  newHeaders.set('x-locale', locale);

  return NextResponse.next({
    request: {
      headers: newHeaders,
    },
  });
};

const addUser: NextMiddleware = async (request) => {
  // The x-locale header from previous middleware is available here
  const locale = request.headers.get('x-locale');

  const user = await getUser(request);
  const newHeaders = new Headers(request.headers);
  newHeaders.set('x-user-id', user.id);

  return NextResponse.next({
    request: {
      headers: newHeaders,
    },
  });
};

// In your page.tsx
export default async function Page() {
  const headersList = await headers();
  const locale = headersList.get('x-locale'); // Available!
  const userId = headersList.get('x-user-id'); // Available!

  return <div>Locale: {locale}, User: {userId}</div>;
}

Uwaga na dwa różne miejsca. NextResponse.next({ request: { headers } }) przekazuje dane w głąb aplikacji - do kolejnych middleware i do strony. Nagłówki, które mają dotrzeć do przeglądarki, ustawia się w NextResponse.next({ headers: { ... } }).

Argument:  event

Typ: NemoEvent, rozszerza NextFetchEvent

Poza tym, co daje NextFetchEvent, obiekt niesie stan całego przebiegu: wspólny storage, wyłuskane params, metody logowania oraz skip().

Storage

Jak dzielić dane między funkcjami w łańcuchu

Logowanie

Do dyspozycji masz ten sam logger, z którego korzysta samo NEMO:

// Debug logging (only displayed when debug is enabled in config)
event.log("Processing user request", userId);

// Error logging (always displayed)
event.error("Failed to process request", error);

// Warning logging (always displayed)
event.warn("Deprecated feature used", featureName);

Każdy wpis dostaje prefiks [NEMO], więc logi z Twoich funkcji i te z biblioteki czyta się razem.

Pomijanie dalszych funkcji

event.skip() przerywa bieżącą sekcję łańcucha (before, main albo after), nie kończąc przy tym żądania żadną odpowiedzią:

const middleware: NextMiddleware = async (request, event) => {
  // Do some processing
  if (someCondition) {
    event.skip(); // Skip remaining middlewares in this chain section
    // You can still return a response if needed
    return NextResponse.next();
  }

  // This code will execute if skip() was not called
};

Skip z opcją:

Opcja skipAfter rozszerza pominięcie na sekcję after:

const middleware: NextMiddleware = async (request, event) => {
  if (someCondition) {
    // Skip remaining middlewares in current chain AND skip after chain
    event.skip({ skipAfter: true });
    return NextResponse.next();
  }
};

Sekcje łańcucha. skip() działa w obrębie jednej sekcji. Wywołane w main zatrzymuje resztę sekcji main, ale after i tak się wykona - dzięki temu sprzątanie zapisane w after odpala się zawsze.

Wariant event.skip({ skipAfter: true }) zdejmuje jedno i drugie: resztę bieżącej sekcji oraz całą sekcję after.

Kiedy się przydaje:

  • wcześniejsze wyjście, gdy warunek jest spełniony, a przekierowanie byłoby nie na miejscu
  • warunkowe uruchamianie funkcji w zależności od tego, co przyszło w żądaniu
  • oszczędność czasu na pracy, która i tak niczego nie wniesie
  • pominięcie sprzątania w after, kiedy nie ma czego sprzątać

Przykłady:

// Skip only current chain section
const authCheck: NextMiddleware = async (request, event) => {
  const token = request.cookies.get("auth-token");

  if (!token) {
    // Skip remaining auth middlewares, but allow after chain to run
    event.skip();
    return NextResponse.next();
  }

  // Continue with authentication logic
};

// Skip current chain and after chain
const cacheCheck: NextMiddleware = async (request, event) => {
  const cached = await checkCache(request);

  if (cached) {
    // Skip remaining middlewares AND after chain (no cleanup needed)
    event.skip({ skipAfter: true });
    return NextResponse.next({
      headers: { "x-cached": "true" },
    });
  }
};

const adminCheck: NextMiddleware = async (request, event) => {
  // This will not execute if skip() was called in authCheck
  const user = storage.get("user");
  if (!user?.isAdmin) {
    return NextResponse.redirect("/unauthorized");
  }
};

Czy ta strona była pomocna?

M↓obsługiwane.

Spis treści