天天看點

七爪源碼:Next.js Middleware:它的工作原理和 5 個實際用例

作者:莊志炎
七爪源碼:Next.js Middleware:它的工作原理和 5 個實際用例

Next.js 中的 Middleware 是什麼?

從 v. 12.0.0(從 12.2.0 開始穩定),Next.js 添加了 Middleware。 Middleware(就像在許多其他架構中一樣)是一種在使用者請求到達實際頁面之前攔截它的方法。 簡而言之,它是位于伺服器和前端之間的一段代碼。 中間件在使用者發出的每個請求之前運作(但它可以被過濾以在特定頁面上工作),可以從使用者請求中讀取資料,執行功能管道并傳回給用戶端。

Middleware 如何在 Next.js 中工作

在每個頁面的項目根目錄中放置一個 middleware.ts 檔案(這将導出一個名為函數的中間件),并且您網站的 API 将在到達頁面之前執行 middleware 管道。 下面是最基本的中間件,它隻是簡單地記錄使用者請求(這樣你就可以看到請求是由什麼組成的),什麼都不做,然後傳回頁面(使用 NextResponse.next()

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware (request: NextRequest) {
  console.log(request);
  return NextResponse.next()
}           

除了使用 next() 傳回頁面之外,NextResponse 還可以設定和擷取 cookie、設定标頭以及将請求重定向或重寫到另一個 URL。

此外,中間件隻能使用比對器或基于請求中找到的路徑名的條件語句在特定頁面上運作

由于中間件在将頁面發送給使用者之前運作,它不會影響頁面的呈現方式,是以它适用于 CSR、SSR、SSG、ISR。

通過中間件利用請求将允許我們在頁面中沒有伺服器端代碼的情況下操縱響應。

重定向和重寫

有時您會更改頁面的 URL 或(更糟糕的是)網站部分的完整結構,但您不想失去獲得的 SEO 分數。 大多數指南會告訴您,您需要将舊 URL 重定向到新 URL。 通過将所有比對模式重定向到其他地方,您可以使用中間件輕松實作它。 例如,您可能希望從 /blog/date/post 這樣的結構移動到 /content/post 這樣的結構。 新結構到位後,使用中間件執行重定向:

// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
  const newurl=generateNewUrl(request.url)
  return NextResponse.redirect(new URL(newurl, request.url));
}


export const config = {
  matcher: '/blog/:path*',
};

function generateNewUrl(url) {
    //Logic for redirect
    //...
    return newurl;
}           

另一種情況是重寫内容(保留 URL,但顯示不同的頁面)。 例如,您有一個餐廳網站,其中有一個頁面(我們稱之為 /about),您可以在其中顯示營業日期和時間。 在短時間内,您的時間表會發生變化,您不想編輯關于頁面,但您也不想更改位址。 使用臨時資料建立一個新頁面 /about-temp 并使用中間件将 /about 頁面重寫為 /about-temp:

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware (request: NextRequest) {
    const url = request.nextUrl.clone()
    url.pathname = '/about-temp'
    return NextResponse.rewrite(url)
  }
}

export const config = {
  matcher: ['/about'],
}           

我們使用 clone() 來保持查詢字元串和其他 url 部分不變

驗證

從基本使用者/密碼,以這種方式使用 401 狀态:

import { NextRequest, NextResponse } from 'next/server';

export function middleware(req) {
  const basicAuth = req.headers.get('authorization');

  if (basicAuth) {
    const auth = basicAuth.split(' ')[1];
    const [user, pwd] = Buffer.from(auth, 'base64').toString().split(':');

    if (user === 'user' && pwd === 'password') {
      return NextResponse.next();
    }
  }

  return new Response('Auth required', {
    status: 401,
    headers: {
      'WWW-Authenticate': 'Basic realm="Secure Area"',
    },
  });
}           

更複雜的授權/身份驗證模式。 由于您可以從請求中讀取标頭和 cookie,是以您可能可以使用它們進行身份驗證。 在以下示例中,我們正在讀取 Bearer 身份驗證令牌,如果使用者被授權,我們将顯示該頁面,否則,我們會将用戶端重定向到登入頁面:

import { NextRequest, NextResponse } from 'next/server';

export function middleware(req) {
  const basicAuth = req.headers.get('authorization');
  //auhorization: Beaer <token>
  if (basicAuth) {
    const auth = basicAuth.split(' ')[1];
    const token = Buffer.from(auth, 'base64').toString().split(' ');
    const validToken=veryToken(token)

    if (validToken) {
      return NextResponse.next();
    }
  }

  return NextResponse.redirect(new URL('/signin', request.url))
}

function validateToken(token) {
  //logic to validate token
  return true
}

export const config = {
  matcher: '/dashboard/:path*',
}           

分期

在開發網站時,您可能希望向使用者展示“建設中”頁面,但同時,您需要向客戶展示網站的進度。 您可以将密鑰放在 URL 的末尾,讓中間件在第一次通路時檢查密鑰是否存在,如果密鑰存在,則設定會話 cookie,所有其他請求都将檢查 cookie。 如果 cookie 不存在,使用者将被重定向(或重寫)到“建設中”頁面:

import { NextResponse } from 'next/server';

export function middleware(request) {
  const key=req.nextUrl.searchParams.get('secretkey');
  const cookie = request.cookies.get('auth')
  if (key) {
     const response = NextResponse.next();
     response.cookies.set('auth', 'OK');
     return response;
  }
  if (cookie===OK) {
    return NextResponse.next();
  }
  return NextResponse.redirect('/under-construction', request.url));
}           

個性化

根據 cookie(或其他請求參數),您可以将使用者重定向到不同的頁面。 假設您有一個電子商務并将您的客戶分為四組。 您可以建立 4 個具有不同優惠和 CTA 的不同首頁,并根據使用者組顯示它們:

import { NextResponse } from 'next/server';

export function middleware(request) {
  const cookie = request.cookies.get('group')
  if (group===1) {
     const url = request.nextUrl.clone()
    url.pathname = '/offer-1'
    return NextResponse.rewrite(url)
  }
  if (group===2) {
     const url = request.nextUrl.clone()
    url.pathname = '/offer-2'
    return NextResponse.rewrite(url)
  }
  if (group===3) {
     const url = request.nextUrl.clone()
    url.pathname = '/offer-3'
    return NextResponse.rewrite(url)
  }
  if (group===4) {
     const url = request.nextUrl.clone()
    url.pathname = '/offer-4'
    return NextResponse.rewrite(url)
  }

  //User with no cookie will get default homepage
  return NextResponse.next();
}

export const config = {
  matcher: '/',
};           

機器人阻塞

由于您可以閱讀 User-Agent,是以您可以将 BOT 和收割機重定向到 404 頁面:

import { NextRequest, NextResponse } from 'next/server'


export default async function middleware(req: NextRequest) {
  const bot=checkBot(req.headers.user-agent);
  
  if (bot=== true) {
    const url = req.nextUrl;
    url.pathname = `/404`;
    return NextResponse.rewrite(url);
  }
  return NextResponse.next();
}

function checkBot(useragent) {
  //logic to check for a bot user agent
  return true;
}           

更多用途

由于您可以通路用戶端請求(包括标頭、cookie、地理位置和更多資料),是以您可以在向使用者顯示頁面之前執行很多操作。 您可以真正根據語言、cookie、位置、頁面請求本身進行深度個性化。 您可以記錄對統計和調試的請求、對頁面的通路計數等。 部署到 Vercel 後,中間件功能非常快,因為它們部署在 Edge 上。 比在頁面級别擁有相同的代碼要快得多,不考慮在更進階别,它們可以通路更多的請求資料,并且不需要為每個頁面複制它們。

關注七爪網,擷取更多APP/小程式/網站源碼資源!

繼續閱讀