NextJS中间件在访问ISR页面时被多次调用。

huangapple go评论62阅读模式
英文:

NextJS middleware called multiple times when accessing an ISR page

问题

export async function middleware(
  request: NextRequest,
  fetchEvent: NextFetchEvent
) {
  console.log('request.nextUrl.pathname', request.nextUrl.pathname);
  if (request.nextUrl.pathname.split('/').length === 2) {
    return linkVisitMiddleware(request, fetchEvent);
  }
}

这个中间件在调用时会出现2到3次(不知道为什么这些数字会波动)。

上面的日志会在服务器上打印出以下内容:

request.nextUrl.pathname /slug
request.nextUrl.pathname /slug
request.nextUrl.pathname /slug

奇怪的是,这只会在生产环境 (next start) 中发生。在本地环境 (next serve) 中,它会如预期地运行。

我看到了关于 reactStrictMode 的一些提及,但那并没有帮助 NextJS中间件在访问ISR页面时被多次调用。

编辑:

在 NextJS 版本 13.1.113.1.613.3.0 上进行了测试。

英文:

I have a NextJS application that hosts an ISR page that fetches data from Supabase. There's a middleware that records a page visit before rendering the page.

export async function middleware(
  request: NextRequest,
  fetchEvent: NextFetchEvent
) {
  console.log('request.nextUrl.pathname', request.nextUrl.pathname);
  if (request.nextUrl.pathname.split('/').length === 2) {
    return linkVisitMiddleware(request, fetchEvent);
  }
}

The issue the middleware is called 2 or 3 times (dont' know why these numbers fluctuate).

The above logging will print the following on the server:

request.nextUrl.pathname /slug
request.nextUrl.pathname /slug
request.nextUrl.pathname /slug

The weird thing, this happens only in production (next start). Locally (next serve), it runs once as expected.

I saw a couple of mentions about reactStrictMode, but that didn't help NextJS中间件在访问ISR页面时被多次调用。

Edit:

Tested on NextJS 13.1.1, 13.1.6 & 13.3.0

答案1

得分: 1

在经过数小时的调试后,我找到了问题所在。

ISR 页面正在使用回退策略 {fallback: true},因此它尝试请求 HTML 页面,如果不可用,则再次访问相同的路由以获取数据。

为了规避这个问题,我发现 NextJS 使用头部来区分请求的类型。以下是我的解决方案:

import { linksMiddleware } from 'middleware/links';
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server';

export async function middleware(
  request: NextRequest,
  fetchEvent: NextFetchEvent
) {
  // 这些方法用于访问区域路由时,我们将它们丢弃
  if (request.method === 'OPTIONS' || request.method === 'HEAD') {
    return NextResponse.next();
  }

  // 捕获 URL 区域(如果有)和 id。例如:/ar/random-id 或 /random-id
  const pathParams = request.nextUrl.pathname.split('/');

  if (pathParams.length === 2 && isDataRequest(request)) {
    // 这意味着我们正在访问实体主页
    return linksMiddleware(request, fetchEvent, 'entity');
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    // 这是为了防止在 API 路由和开发过程中运行中间件
    '/((?!api|_next/static|_next/image|favicon.ico|favicon.svg).*)&',
  ],
};

/**
 * 检查请求是否为回退页面的数据请求
 * 初始请求中将缺少此标头
 */
const isDataRequest = (request: NextRequest): boolean =>
  request.headers.get('x-nextjs-data') === '1' ||
  // 在开发模式下,由于没有回退逻辑(类似于 SSR),因此标头将不会出现
  process.env.NODE_ENV === 'development';

此外,我还添加了对 HTTP 方法的检查,以排除区域路由的情况,但保留了 OPTIONSHEAD

英文:

After hours of debugging, I found the culprit.

The ISR page is using the fallback strategy as {fallback: true} and because of this, it tries to request the HTML page, and if not available, it accesses the same route again to fetch the data.

To circumvent this, I found that NextJS employs headers to differentiate between the type of the request. Here's my solution:

import { linksMiddleware } from 'middleware/links';
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server';

export async function middleware(
  request: NextRequest,
  fetchEvent: NextFetchEvent
) {
  // These methods are used when accessing locale routes, so we discard them
  if (request.method === 'OPTIONS' || request.method === 'HEAD') {
    return NextResponse.next();
  }

  //Capture URL locale (if any) and id. For example: /ar/random-id or /random-id
  const pathParams = request.nextUrl.pathname.split('/');

  if (pathParams.length === 2 && isDataRequest(request)) {
    // This means that we are accessing the entity homepage
    return linksMiddleware(request, fetchEvent, 'entity');
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    // This is to prevent the middleware from running on the API routes and during development
    '/((?!api|_next/static|_next/image|favicon.ico|favicon.svg).*)',
  ],
};

/**
 * Check if the request is a data request for cases of fallback pages
 * This header is absent in the initial request
 */
const isDataRequest = (request: NextRequest): boolean =>
  request.headers.get('x-nextjs-data') === '1' ||
  // The header is absent in development mode since there's no fallback logic (SSR-like)
  process.env.NODE_ENV === 'development';

Also I added a check for the HTTP method for the cases of locale routing excluding OPTIONS and HEAD.

huangapple
  • 本文由 发表于 2023年4月7日 03:19:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75953035.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定