[NextJS] Middleware

agert·2023년 8월 13일
1

NextJS

목록 보기
1/2

Edge Function

  • Middleware는 Edge function을 사용
  • Edge Function은 매우 빠르고, 동적으로 개인화시킨 데이터를 전달할 수 있다. (CDN + 동적 서버)
  • 대부분의 Node.js API를 사용할 수 없다.

How Edge Functions work

  • Edge Functions는 vercel의 Edge Runtime을 사용
    • Edge Runtime은 V8, WebAssembly 엔진 기반
  • Edge Network가 globally 구축되어있다.

Middleware

Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.

  • 미들웨어를 통해 요청이 생성되고, 요청이 완료되기 전에 code를 실행할 수 있다.
  • 요청, 응답의 데이터를 읽고, 변조할 수 있고, 그 데이터를 바탕으로 원하는 코드를 실행시킬 수 있다.

Middleware usage

Middleware를 활용해서 어떤 문제를 해결하고 있을까?

초기 화면 리소스 개선 (링크)

  • 해당 웹 서비스는 desktop, mobile 모두에 대응한 react 프로젝트를 관리하고 있는 것으로 보이는데, userAgent를 읽어서 초기 리소스를 개선한 사례를 소개하고 있다.

As-is

메인페이지를 모바일에서 접속시 무려 61.6MB의 리소스를 다운로드

  • 모바일 / 데스크탑 접속 사용자에게 각각 다른 화면을 보여줘야 한다.
  • 일단 페이지에 진입 후, 첫 SSG 에셋을 다운로드 받고 실행시켜서 useEffect에서 화면 사이즈로 분기하여 모바일 화면, 데스크탑 화면을 보여준다.
  • 데스크탑 리소스를 실행하면 useEffect에서 모바일 유저임을 확인하고, 이를 모바일용 리소스를 추가 요청하게 된다.

To-be

  • 메인 페이지(/)로 요청이 오면, 미들웨어 실행, 요청 헤더에서 userAgent를 파싱
  • mobile, desktop인지 확인하고, 이를 pathname에 추가
  • 코드 내에서는 _viewport 이름으로 하위 폴더를 만들고, _viewport/desktop.tsx, _viewport/mobile.tsx 파일을 만듦
  • 코드 예시
         import { NextResponse, userAgent } from 'next/server'
         import type { NextRequest } from 'next/server'
         
         export function middleware(request: NextRequest) {
           if (request.nextUrl.pathname === '/') {
             const { device } = userAgent(request)
             const viewport = device.type === 'mobile' ? 'mobile' : 'desktop'
             request.nextUrl.pathname = `_viewport/${viewport}`
             return NextResponse.rewrite(request.nextUrl)
           }
           return NextResponse.next();
         }
         
         // matcher에 해당하는 경로일 경우만 위 middleware가 실행된다. (과부하 방지)
         export const config = {
           matcher: ['/'],
         }```
         

결과

  • 모바일 기준으로 홈 화면 리소스 60% 정도 감소

Rate Limiting

  • client가 어플리케이션에 접근할 때, 요청 수를 제한할 수 있다.
    • 디도스 공격에 대응할 수 있음.
  • 예시 코드 (링크)
    import type { NextRequest } from 'next/server'
    import { Ratelimit } from '@upstash/ratelimit'
    import { kv } from '@vercel/kv'
    
    const ratelimit = new Ratelimit({
      redis: kv,
      // 5 requests from the same IP in 10 seconds
      limiter: Ratelimit.slidingWindow(5, '10 s'),
    })
    
    export const config = {
      runtime: 'edge',
    }
    
    export default async function handler(request: NextRequest) {
      // You could alternatively limit based on user ID or similar
      const ip = request.ip ?? '127.0.0.1'
      const { limit, reset, remaining } = await ratelimit.limit(ip)
    
      return new Response(JSON.stringify({ success: true }), {
        status: 200,
        headers: {
          'X-RateLimit-Limit': limit.toString(),
          'X-RateLimit-Remaining': remaining.toString(),
          'X-RateLimit-Reset': reset.toString(),
        },
      })
    }

Bot Protection

  • 최근, 서비스에 URL brute-forcing 공격을 시도하는 것이 모니터링에 포착됐는데, 이를 해결하기 위해 NextJS middleware를 사용할 수도 있다.
  • 예시 코드 (링크)
    import { NextRequest, NextResponse } from 'next/server'
    import {
      BOTD_DEFAULT_PATH,
      BOTD_DEFAULT_URL,
      BOTD_PROXY_API,
      BOTD_PROXY_JS,
    } from './constants'
    
    type NextURL = Parameters<typeof NextResponse['rewrite']>[0]
    
    type Proxies = {
      [key: string]: (req: NextRequest) => NextURL
    }
    
    export const PROXIES: Proxies = {
      [BOTD_PROXY_JS]: () =>
        'https://cdn.jsdelivr.net/npm/@fpjs-incubator/botd-agent@0/dist/botd.min.js',
      [BOTD_PROXY_API + 'detect']: (req) =>
        `${BOTD_DEFAULT_URL}${BOTD_DEFAULT_PATH}detect${req.nextUrl.search ?? ''}`,
    }
    
    /**
     * Proxy Botd scripts and browser calls
     * Note: We can't change headers when proxying, so for botd we're going to
     * their domain directly instead of using this proxy because we can't send
     * the `botd-client-ip` header
     */
    export default function botdProxy(req: NextRequest) {
      const proxy = PROXIES[req.nextUrl.pathname]
    
      if (proxy) {
        return NextResponse.rewrite(proxy(req))
      }
    }

References

Next.js Middleware 업무에 활용하기

https://irondeveloper.tistory.com/10

Edge Runtime

https://vercel.com/blog/introducing-the-edge-runtime

Edge function examples

https://github.com/vercel/examples/tree/main/edge-functions

profile
스타트업에서 프러덕트 개발자로 일하고 있습니다.

2개의 댓글

comment-user-thumbnail
2023년 8월 13일

훌륭한 글 감사드립니다.

1개의 답글