Next.js Routing: 파일 기반 라우팅과 서버 API Routes

박은정·2025년 1월 31일
0

Next

목록 보기
7/7
post-thumbnail

1️⃣ Next.js Routing 개요

Next.js는 파일 기반 라우팅 시스템을 사용합니다. 즉, 폴더 구조가 곧 URL 경로가 되며, 개발자가 별도로 라우팅을 설정할 필요가 없이, pages/ 폴더에 파일을 생성하는 것만으로도 직관적이고 효율적인 라우팅이 자동으로 제공합니다.

URL파일 경로
//pages/index.tsx
/about/pages/about.tsx
/contact/pages/contact.tsx

2️⃣ 파일 기반 라우팅 (Static Routing)

Next.js의 라우팅 시스템은 여러 기능을 통해 확장될 수 있으며, 다음과 같이 구분됩니다.

✅ 페이지 기반 라우팅 (Page Router)

pages/ 폴더 기반으로 동작
데이터 패칭 방식: getServerSideProps, getStaticProps

✅ Next.js 13+ 의 앱 기반 라우팅 (App Router)

app/ 폴더 기반으로 동작
리액트 서버 컴포넌트 및 서버액션을 지원하고, 더 강력한 레이아웃 및 상태 관리 기능을 제공

3️⃣ 동적 라우팅 (Dynamic Routing)

✅ 기본적인 동적 라우팅

파일 이름을 대괄호로 감싸면 해당 부분이 동적으로 변할 수 있습니다.

파일: /pages/post/[id].tsx
URL 예시: /post/1, /post/2, /post/title

✅ Catch-All Routes (다중 경로 매칭)

Catch-All-Routes는 Next.js에서 동적 라우팅을 확장하여 여러 개의 경로 세그먼트를 한 번에 처리할 수 있도록 해주는 기능입니다.

필수 Catch-All Routes([…param] )

하나 이상의 경로 매개변수가 반드시 필요하며, 해당 값이 없으면 404 에러를 발생합니다.
예시: pages/blog/[…slug].tsx/blog/hello/world 는 가능하지만, 루트 경로인 /blog/는 404 오류 발생합니다.

선택적 Catch-All Routes([[…param]])

경로 매개변수가 없어도 실행되며, 루트 경로에서도 동일한 컴포넌트를 활용할 수 있습니다.
예시: pages/blog/[[…param]].tsxblog 도 정상적으로 처리 가능

4️⃣ API Routes (백엔드 API 핸들링, 서버리스 API 기능)

Next.js의 API Routes는 서버에서 실행되는 API 엔드포인트를 쉽게 만들 수 있는 기능입니다.
일반 서버 뿐만 아니라 Vercel과 같은 서버리스 환경에서도 동작할 수 있기 때문에, Next.js 프로젝트 안에서도 백엔드 기능을 구현할 수 있습니다.

✅ API Routes의 동작 방식

Next.js 에서 api 폴더 내부에 파일을 생성하면, 해당 파일이 자동으로 API 엔드포인트가 됩니다.
API Route 내부의 함수는 요청과 응답 객체를 받아 HTTP 요청을 처리합니다.

Next.js의 API Routes는 일반 서버 환경과 서버리스 환경 모두에서 작동할 수 있도록 설계된 기능입니다. 다만, 배포되는 환경이 서버리스 환경이라면 요청할 때만 실행되고, 일반 서버 환경이라면 지속적인 실행을 한다는 점에서 동작 방식이 다릅니다.

서버리스 환경에서의 API Routes 동작

Next.js가 Vercel, AWS Lambda, Cloud Functions 같은 서버리스 플랫폼에서 실행될 경우 다음과 같이 동작합니다.

  1. API Route의 각 핸들러 함수는 서버리스 함수로 배포됩니다.
  2. 서버리스의 자동 스케일링의 특성으로 요청이 있을 때만 함수가 실행되고, 요청이 없으면 리소스를 사용하지 않습니다.
  3. 서버리스 환경에서는 상태를 유지하지 않기 때문에, 필요하면 데이터베이스나 외부 API를 활용해야 합니다.

⭐️ 서버리스 환경에서는 상태가 유지되지 않는 이유

서버리스는 요청이 들어올 때마다 새로운 인스턴스를 생성하고, 요청이 끝나면 생성했던 인스턴스를 종료합니다. 즉, 서버가 항상 실행되고 있는 것이 아니라, 요청이 있을 때만 실행되기 때문에 메모리에 상태를 저장해도 다음 요청에는 새로운 실행 환경이 할당되기 때문에 상태가 유지되지 않습니다.

일반 서버 환경에서의 API Routes 동작

Next.js가 Node.js 서버에서 실행될 경우 다음과 같이 동작합니다.

  1. API Routes는 Express 같은 일반적인 백엔드 서버처럼 동작합니다.
  2. 서버가 항상 실행되며, 요청이 들어오면 API 핸들러가 실행됩니다.
  3. 메모리 상태를 유지할 수 있고, 장기 실행 프로세스도 가능합니다.

⭐️ 서버에서 상태를 유지한다?

저는 그동안 클라이언트에서 상태를 다루는 걸로 알고 있었지만, 사실 서버에서도 서버를 유지하는 경우가 있습니다.

여기서 말하는 상태는 글로벌 변수나 캐시같이 어떤 요청과 다음 요청 사이에 유지되는 데이터를 의미합니다. 서버리스 환경에서는 이런 상태를 유지할 수 없고, 일반적인 서버 환경에서는 상태를 유지할 수 있습니다.

서버가 계속 실행 중이라면 counter 변수의 값은 유지되기 때문에, 요청이 여러 번 들어와도 이전의 counter 값이 유지되면서 증가합니다.
서버가 재시작되거나, 서버리스 환경에서는 매번 새로운 인스턴스에서 실행되기 때문에 counter 값은 초기화됩니다.

// Node.js Express 서버
let counter = 0;
app.get('/increase', (req, res) => {
	counter++;
    res.send(`Counter: ${counter}`);
});

✅ API Route 생성방법

Page Router 에서 API Route 생성

  1. 파일 경로: src/pages/엔드포인트.ts
  2. Express.js 스타일로 req, res 객체를 활용해서 응답 반환
  3. 기본적인 handler 함수를 export default 로 정의함
  4. API Route는 자동으로 서버리스 함수로 실행됨
// src/pages/api/hello.ts
export default function handler(req, res){
	res.status(200).json({ message: ‘Page Router 예시’ });
}

App Router 에서 API Route 생성

  1. 파일 경로: src/app/api/엔드포인트/route.ts
  2. 서버 컴포넌트를 활용하고, req, res 대신 웹 표준 Request, Response 객체 사용
  3. HTTP 메서드별 함수(export function GET()) 구조 사용
  4. 더 유연한 서버리스 환경과 호환됨
// src/app/api/hello/route.ts
export async function GET(req) {
	const body = { message: ‘App Router 예시’ });
	const options = { headers: { ‘Content-Type’: ‘application/json’ }, };
	return new Response(JSON.stringfy(body, options);
}

5️⃣ 미들웨어 (Middleware)

Next.js 미들웨어는 요청이 최종적으로 페이지나 API 핸들러에 전달되기 전에 실행되는 코드입니다.
요청이 전달되기 전에 실행되는 점을 활용하면 인증 체크, 페이지 리다이렉션, 캐시 정책 설정 등의 작업을 수행하는 데 유용합니다.

미들웨어 활용 예시

인증: 쿠키나 헤더를 확인하여 인증된 사용자만 특정 페이지에 접근하도록 제한
리다이렉션: 특정 경로에 접근하는 사용자를 다른 페이지로 이동
캐시 제어: 응답 헤더를 수정해서 캐시 정책을 제어

// 루트경로에 위치한 middleware.ts
import { NextResponse } from ‘next/server’;
import type { NextRequest } from ‘next/server’;

// auth-token 쿠키를 확인하고, 로그인되지 않은 유저는 자동으로 / 경로로 리다이렉트됩니다.
export function middleware(req: NextRequest) {
	const isLogin = req.cookies.get('auth-token’);
	if(!isLogin) return NextResponse.redirect(new URL(/, req.url);

	// HTTP를 HTTPS로 강제 변환
	if(req.nextUrl.protocol === ‘http:) return NextResponse.redirect(req.url.replace(‘http:, ‘https:));

	const response = NextResponse.next();
	const pathname = req.nextUrl.pathname;

	if(pathname.startsWith(/no-cache’)) {
		// 브라우저와 프록시에서 캐싱되지 않도록 설정
		response.headers.set(‘Cache-Control’,’no-store, no-cache, must-revalidate, proxy-revalidate’);
	}

	if(pathname.startsWith(/static)) {
		// 1년 (=31536000초) 동안 캐싱되며 변경되지 않음(immutable)으로 설정
		response.headers.set(‘Cache-Control’,public, max-age=31536000, immutable’);
	}

	if(pathname.startsWith(/api/secure’)) {
		const token = req.headers.get(‘Authorization’);		
		// 올바른 토큰이 없으면 401 응답을 반환함
		if(!token || token !== ‘Bearer token-name’) return new Response(‘Unauthorized’, { status: 401 });
	}

	return response;
}

// 특졀 경로에서만 미들웨어 실행하도록 설정: /home 페이지에 접근하면 미들웨어가 실행됩니다.
export const config = {
	matcher: [/home’],
};
profile
새로운 것을 도전하고 배운것을 정리하려 합니다.

0개의 댓글