
실무에서 DX(Developer Experience)를 개선하기 위해 Next.js의 경로를 타입으로 추출해 사용했던 사례를 공유합니다.
이번 글에서는 해당 기능을 왜 개발하게 되었는지와 어떻게 사용하는지에 대해 다룹니다. 주관적인 경험이 포함되어 있으니 피드백은 언제든지 환영합니다!
Next.js에서의 경로 관리 문제


Next.js에서는 Router와 Link를 사용해 경로를 이동합니다. 하지만 두 방법 모두
href
에 경로를 문자열(string)로 전달해야 하며, 이로 인해 다음과 같은 문제가 발생합니다.// 이동해야 하는 경로: '/posts' // router 예시 const router = useRouter(); router.push('/post'); // 에러가 나지 않음 // Link 예시 <Link href={'/post'} />;
위 코드에서 오타로 인해 잘못된 경로
'/post'
를 입력했지만, 컴파일 단계에서는 에러를 감지할 수 없습니다. 브라우저에서 테스트하지 않으면 오류를 발견하기 어렵죠.이 문제를 해결하기 위해 경로를 타입으로 추출하고, 컴파일 단계에서 오류를 감지할 수 있는 방법을 구상했습니다.
Next.js의 라우팅 구조 이해하기
![next.js appRouter [공식 문서]](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F9499dcf8-ef4b-4e36-b059-7802661d5b07%2F18b3a77a-4f07-42b7-8f5c-ae867d4f41f8%2Fimage.png?table=block&id=14f3f2f9-89a6-8038-937e-cf334b7b4eae&cache=v2)
![next.js pageRouter [공식 문서]](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F9499dcf8-ef4b-4e36-b059-7802661d5b07%2Ff306d4db-7fac-4089-b462-638563c2b8af%2Fimage.png?table=block&id=14f3f2f9-89a6-8051-9391-d3c8282a883f&cache=v2)
Next.js는 파일 시스템 기반 라우팅을 사용합니다. 페이지는 두 가지 라우터 방식으로 관리됩니다.
- Page Router
pages
폴더 기반으로 동작하며, 동적 경로는 [folderName]
, [fileName].tsx
로 표현합니다.- App Router
app
폴더 기반으로 동작하며, 동적 경로는 [folderName]
으로 표현합니다.위 구조를 활용하면 정적 경로와 동적 경로를 리스트로 추출할 수 있습니다.
경로 추출 로직 구현
경로를 추출하기 위해 Next.js의 파일 구조를 순회하는 스크립트를 작성했습니다. 여기서는 Node.js의 fs와 path 모듈을 사용합니다.
경로 탐색 로직
다음은 경로를 추출하는 함수입니다.
디렉토리와 파일을 구분하고, 동적 경로와 정적 경로를 처리하는 재귀 구조로 설계되었습니다.
export const getPageRoutes = (dir: string, basePath = ''): string[] => { const entries = fs.readdirSync(dir, { withFileTypes: true }); const routes: string[] = []; for (const entry of entries) { if (entry.isDirectory()) { const segment = entry.name.startsWith('[') && entry.name.endsWith(']') ? '${string}' : entry.name; routes.push(...getPageRoutes(path.join(dir, entry.name), `${basePath}/${segment}`)); } else if (entry.isFile() && entry.name.endsWith('.tsx')) { const fileName = entry.name.replace('.tsx', ''); routes.push(basePath + (fileName === 'index' ? '/' : `/${fileName}`)); } } return routes; };
타입 정의 생성
추출한 경로는 정적 타입과 동적 타입으로 나누어 정의합니다.
type StaticPaths = '/about' | '/contact'; type DynamicPaths = `/user/${string}` | `/product/${number}`;
다음 함수는 경로 리스트를 기반으로 위와 같은 타입을 생성합니다.
export const generateTypeDefinition = (routes: string[]): string => { const staticRoutes = routes.filter((route) => !route.includes('${')); const dynamicRoutes = routes.filter((route) => route.includes('${')); return `// This file is auto-generated. type StaticPaths = ${staticRoutes.map((route) => `| '${route}'`).join('\\n ')}; type DynamicPaths = ${dynamicRoutes.map((route) => `| \\`${route}\\``).join('\\n ')}; `; };
최종 스크립트
위 로직을 조합하여, 파일 시스템에서 경로를 추출하고 이를 타입 파일로 저장하는 스크립트를 작성했습니다.
export const generateRoutes = ( pagesDir: string, outputFile: string ): void => { const routes = getPageRoutes(pagesDir); const typeDefinition = generateTypeDefinition(routes); fs.writeFileSync(outputFile, typeDefinition); console.log(`RoutePath types generated at ${outputFile}`); };
실행하면 다음과 같은 타입 파일이 생성됩니다.
type StaticPaths = | '/home' | '/about'; type DynamicPaths = | `/user/${string}` | `/product/${number}`; type RoutePath = StaticPaths | DynamicPaths;
개선된 DX
동적 경로를 타입으로 추출해주는 작업을 한 이유는 2가지가 있었습니다.
- string타입의 경로로 인한 유저 에러를 제거할 수 있다.
- 타입 자동완성을 통해 빠르게 경로를 찾을 수 있다.
그럼 이제 만들어진 저 경로 타입을 router에 매핑해서 사용해보도록 하겠습니다.
1. 런타임 에러 제거
const router = useRouter(); router.push('/homes'); // 컴파일 단계에서 에러 발생!
추출된 타입을 사용하면 잘못된 경로를 입력했을 때 컴파일 단계에서 오류를 감지할 수 있습니다. 이는 테스트에 의존하던 기존 방식과 달리, 개발 단계에서 문제를 미리 방지할 수 있게 합니다.
2. 타입 자동완성

타입 자동완성을 통해 경로를 빠르게 탐색할 수 있습니다. 오랜만에 프로젝트를 다룰 때도 경로를 일일이 확인할 필요 없이 타입만으로 쉽게 찾을 수 있습니다.
한계와 개선 방향
스크립트 자동 실행
매번 수동으로 스크립트를 실행하는 것은 번거로우므로, 빌드 과정에 포함시키는 것이 이상적입니다.
"scripts": { "generate:routes": "node scripts/generate-route-paths.js" }
또는 CLI 형태로 제공해 프로젝트에 쉽게 통합할 수도 있습니다.
"generate:routes": "generate-router ./pages ./src/routes.d.ts"
Query String 처리
Query String(
/home?tab=profile
)의 경우 현재는 아래와 같이 처리합니다.type RoutePath = StaticPaths | DynamicPaths | `${StaticPaths}?${string}`;
보다 세밀한 타입 정의 방법이나 DX 개선 아이디어가 있다면 언제든 의견 부탁드립니다. (사실 Query String을 처리하는 다른 좋은 방법을 알기 위해 쓰는 포스팅..)
마무리
Next.js에서 경로를 타입으로 추출하면 다음과 같은 이점이 있습니다.
- 런타임 에러를 컴파일 단계에서 제거
- 타입 자동완성을 통한 생산성 향상
이 글에서 소개한 로직은 Github 저장소에도 공개되어 있고, NPM에도 배포가 되어있습니다. 필요하시다면 사용해보시고 피드백 부탁드립니다.
// 설치 npm install generate-router // CLI 간단한 사용 npx generate-router ./pages ./types/routes.d.ts // SCRIPT로 사용 "scripts": { "generate:routes": "generate-router ./pages ./src/routes.d.ts" }
DX를 개선하는 더 나은 방법을 함께 고민해볼 수 있길 바랍니다. 감사합니다!