Next.js의 파일 시스템 기반 라우팅은 편리합니다.
파일 시스템 기반 라우팅은 프로젝트의 폴더와 파일 구조에 따라 자동으로 라우트를 설정해 주는 기능입니다.
예를 들어, app/about/page.js 파일을 생성하면 자동으로 /about 경로가 설정됩니다.
어떻게 폴더와 파일만 생성했는데 라우트가 등록된 걸까요?
파일 시스템 기반 라우터의 동작 방식을 간단한 express 서버 예시 코드를 통해 알아보겠습니다.
예제 프로젝트의 디렉토리 구조는 다음과 같습니다.
my-routing-app/
├── app/
│ ├── about/
│ │ └── page.js
│ ├── product/
│ │ └── [id]/
│ │ └── page.js
│ └── page.js
└── server.js
server.js를 제외하면 app 라우터를 이용한 Next.js의 구조와 동일합니다.
파일의 역할
각 page.js 파일들은 단순히 HTML 콘텐츠를 반환하는 역할만 수행합니다.
app/page.js
module.exports = (req, res) => {
const content = `
<h1>Home Page</h1>
<p>Welcome to the home page!</p>
<a href="/about">About</a>
<a href="/product/1">Product 1</a>
<a href="/product/2">Product 2</a>
`;
res.send(content);
};
app/about/page.js
module.exports = (req, res) => {
const content = `
<h1>About Page</h1>
<p>This is the about page.</p>
<a href="/">Home</a>
`;
res.send(content);
};
app/product/[id]/page.js
module.exports = (req, res) => {
const { id } = req.params; // URL 파라미터에서 ID를 가져옴
const content = `
<h1>Product Page</h1>
<p>This is the page for product with ID: ${id}</p>
<a href="/">Home</a>
`;
res.send(content);
};
서버 동작
server.js
const express = require("express");
const fs = require("fs");
const path = require("path");
const app = express();
const port = 3000;
const routeDir = path.join(__dirname, "app");
function registerRoutes(dir, baseRoute = "") {
fs.readdirSync(dir).forEach((file) => {
const fullPath = path.join(dir, file);
const routePath = path
.join(baseRoute, file === "page.js" ? "" : file)
.replace(/\\\\/g, "/");
if (fs.lstatSync(fullPath).isDirectory()) {
registerRoutes(fullPath, routePath);
} else if (file === "page.js") {
const routeHandler = require(fullPath);
const finalRoute =
routePath === "" || routePath === "." ? "/" : `/${routePath}`;
if (finalRoute.includes("[id]")) {
const dynamicRoute = finalRoute.replace("[id]", ":id");
app.get(dynamicRoute, routeHandler);
} else {
app.get(finalRoute, routeHandler);
}
}
});
}
registerRoutes(routeDir);
app.listen(port, () => {
console.log(`Server is running at <http://localhost>:${port}`);
});
서버 설정
Express 서버를 설정하고, 동적 라우팅을 처리하기 위한 registerRoutes 함수를 정의합니다.
라우트 등록
registerRoutes 함수는 app 디렉토리디렉터리 내의 모든 파일과 디렉터리를 재귀적으로 탐색하여 라우트를 등록합니다.
여기서 중요한건 fs.lstatSync를 사용하여 파일 또는 디렉터리인지 확인한다는 점입니다.
탐색 중인 경로가 디렉터리인 경우 재귀적으로 해당 디렉터리 내의 파일과 디렉터리를 탐색합니다.
그리고 page.js 파일을 만났을 경우에만 해당 파일을 라우트 핸들러로 등록하게 됩니다.
동적 라우팅
동적 라우팅도 경로를 검사하는 같은 원리로 구현 할 수 있습니다.
app/product/[id]/page.js처럼 경로에 [id]가 포함되어 있으면 Express 라우트 형식인 :id로 변환합니다.
Express에서 :id는 URL 경로의 매개변수를 나타내며, 요청이 들어올 때 해당 매개변수를 추출하여 사용할 수 있습니다.
실제 동작 시연
$node server.js 명령어로 서버를 실행하고 브라우저에서 각 경로로 접근하면 파일 시스템에 있는 page.js 파일들이 잘 불러와지는 걸 확인할 수 있습니다.
https://github.com/kihyeoon/file-system-based-router-sample
결론
Next.js에서 모든 파일을 라우터로 등록하는게 아니라 page.js만 골라서 등록하고 폴더와 파일 이름을 시멘틱 하게 사용한다는 점이 신기했었습니다.
파일 시스템 기반 라우터를 실제로 구현해보면서 마법처럼 파일이 분류되는 게 아니라 파일 시스템을 재귀적으로 탐색하면서 특정 키워드의 폴더와 파일을 검사한다는 것을 알게 되었습니다.
프레임워크가 주는 편의성 속에 다양한 기능들이 숨어있다는 것을 다시 한번 깨닫는 경험이 되었습니다.