안녕하세요, 한국신용데이터 프론트엔드 개발자 이원국(Lee)입니다.
Next.js 애플리케이션의 페이지 메타 정보를 하나의 JSON 파일에서 일률적으로 관리하기 위해 next-pathmap이라는 cli 라이브러리를 재미삼아 만들어 보았습니다. 어떤 필요에 의해 만들게 되었고 어떻게 만들었는지 소개해드립니다.
https://www.npmjs.com/package/next-pathmap
요약
- Next.js의 모든 페이지마다 메타 정보를 일률적으로 관리하기 위한 라이브러리를 만들었습니다.
- page directory 또는 page extension 패턴에 맞추어 프로젝트 폴더를 파싱하고 JSON으로 path별 메타 정보를 담는 객체를 만들어줍니다.
- 사내 자원 명명 컨벤션에 맞춘 PV 이벤트 트래킹용 페이지 명칭, PV 추적 여부, 서비스 카테고리 등을 하나의 파일에서 누락 없이 관리하기 위해 만들었습니다.
feat-1. Next.js의 파일 시스템 기반 라우팅(src/pages/*)을 활용
파일 시스템 상 pages 하위 디렉토리 구조와 확장명에 따라 페이지가 구성되는 next.js의 특징을 활용하여, 페이지 컴포넌트 파일을 파싱합니다.
.
├─ insurance
│ ├─ index.page.tsx
│ ├─ join
│ │ ├─ index.page.tsx # 설정/관리자/화면_1
│ │ ├─ product-A.page.tsx # 설정/관리자/화면_1
│ │ ├─ product-B.page.tsx # 설정/관리자/화면_2
│ │ └─ ...
│ │
│ ├─ submit
│ │ ├─ index.page.tsx
│ │ ...
│ └─ ...
...
feat-2. 프롬프트 입력 또는 config 파일을 통한 라이브러리 세팅
프롬프트 입력 또는 config 파일을 통해 페이지 루트의 위치, 만들어진 pathmap의 저장 위치 및 파일명, 파싱 포함 또는 제외 스코프, 맵핑할 페이지의 메타 데이터 구조, 상위 위계(카테고리) 라우트의 별칭을 설정할 수 있습니다.
const PathmapConfig = {
pathToPages: 'src/pages',
pathToSave: 'src/pathmap/pathmap.json',
includes: ['insurance/**/*.page.{ts,tsx}'],
excludes: ['!**/_*', '!**/!api'],
schema: {
alias: "",
trackPageView: true,
useCategories: true,
passQueryParams: true
},
categories: [
{
insurance: '금융/보험'
}
]
};
module.exports = PathmapConfig;feat-3. JSON으로 출력되는 pathmap 파일
(2)의 설정에 따라 지정된 위치에 JSON으로 라우트 파싱 데이터에 입력된 스킴을 맵핑한 output을 저장합니다.
여기서 한번 지정된 설정 값은 파싱을 다시 실행해도 덮어쓰지 않고 새로운 라우트에 대해서만 구조를 갱신합니다.
{
"/insurance": {
"alias": "보험",
"trackPageView": true,
"categories": ["금융/보험"],
"query": []
},
"/insurance/join": {
"alias": "보험/가입",
"trackPageView": true,
"categories": ["금융/보험"],
"query": []
},
"/insurance/join/[:product]": {
"alias": "보험/가입/{{product}}", // 동적 할당 값
"trackPageView": true,
"categories": ["금융/보험"],
"query": ["product"]
},위 산출물을 통해 PV 한글 이벤트명을 자동으로 참조하거나, 페이지마다 지정해야 하는 특정한 공통 설정 등을 관리할 수 있습니다. (특정 이벤트 추적 여부, 동적 쿼리, 특정 기능 사용 여부 등)
feat-4. git hook, Ci 등 자동화에 연계시켜 사용할 목적
next-pathmap 라이브러리가 수행하는 기능은 너무 간단합니다. 파일 시스템을 특정 규칙에 따라 파싱하고, JSON 포맷의 데이터로 만들어주는 것입니다.
해당 라이브러리의 제작 목적은 페이지와 관련된 이벤트 명명 규칙과 메타 태그를 사용한 페이지 명세 등을 일관된 규칙으로 관리하는것에 의의가 있습니다.
PV 이벤트 누락, 이벤트명 컨벤션이 틀어지는 문제, 더 이상 사용하지 않지만 제거되지 않은 페이지 등 관리의 필요성을 느끼게 되었고, 자동으로 pathmap을 생성해서 single source에서 라우트를 관리할 수 있는 사내 표준을 만들고 싶었습니다.
안녕하세요, 한국신용데이터 프론트엔드 개발자 이원국(Lee)입니다.
Next.js 애플리케이션의 페이지 메타 정보를 하나의 JSON 파일에서 일률적으로 관리하기 위해 next-pathmap이라는 cli 라이브러리를 재미삼아 만들어 보았습니다. 어떤 필요에 의해 만들게 되었고 어떻게 만들었는지 소개해드립니다.
요약
feat-1. Next.js의 파일 시스템 기반 라우팅(src/pages/*)을 활용
파일 시스템 상 pages 하위 디렉토리 구조와 확장명에 따라 페이지가 구성되는 next.js의 특징을 활용하여, 페이지 컴포넌트 파일을 파싱합니다.
feat-2. 프롬프트 입력 또는 config 파일을 통한 라이브러리 세팅
프롬프트 입력 또는 config 파일을 통해 페이지 루트의 위치, 만들어진 pathmap의 저장 위치 및 파일명, 파싱 포함 또는 제외 스코프, 맵핑할 페이지의 메타 데이터 구조, 상위 위계(카테고리) 라우트의 별칭을 설정할 수 있습니다.
const PathmapConfig = {
pathToPages: 'src/pages',
pathToSave: 'src/pathmap/pathmap.json',
includes: ['insurance/**/*.page.{ts,tsx}'],
excludes: ['!**/_*', '!**/!api'],
schema: {
alias: "",
trackPageView: true,
useCategories: true,
passQueryParams: true
},
categories: [
{
insurance: '금융/보험'
}
]
};
module.exports = PathmapConfig;
feat-3. JSON으로 출력되는 pathmap 파일
(2)의 설정에 따라 지정된 위치에 JSON으로 라우트 파싱 데이터에 입력된 스킴을 맵핑한 output을 저장합니다.
여기서 한번 지정된 설정 값은 파싱을 다시 실행해도 덮어쓰지 않고 새로운 라우트에 대해서만 구조를 갱신합니다.
{"/insurance": {
"alias": "보험",
"trackPageView": true,
"categories": ["금융/보험"],
"query": []
},
"/insurance/join": {
"alias": "보험/가입",
"trackPageView": true,
"categories": ["금융/보험"],
"query": []
},
"/insurance/join/[:product]": {
"alias": "보험/가입/{{product}}", // 동적 할당 값
"trackPageView": true,
"categories": ["금융/보험"],
"query": ["product"]
},
위 산출물을 통해 PV 한글 이벤트명을 자동으로 참조하거나, 페이지마다 지정해야 하는 특정한 공통 설정 등을 관리할 수 있습니다. (특정 이벤트 추적 여부, 동적 쿼리, 특정 기능 사용 여부 등)
feat-4. git hook, Ci 등 자동화에 연계시켜 사용할 목적
next-pathmap 라이브러리가 수행하는 기능은 너무 간단합니다. 파일 시스템을 특정 규칙에 따라 파싱하고, JSON 포맷의 데이터로 만들어주는 것입니다.
PV 이벤트 누락, 이벤트명 컨벤션이 틀어지는 문제, 더 이상 사용하지 않지만 제거되지 않은 페이지 등 관리의 필요성을 느끼게 되었고, 자동으로 pathmap을 생성해서 single source에서 라우트를 관리할 수 있는 사내 표준을 만들고 싶었습니다.
어떤 문제를 해결하기 위해 만들었나요?
기존 한글명 디렉토리 구조로부터 파생된 페이지 명명 규칙
저희 프론트엔드는 주요 기술 스택으로 React, Next.js를 사용하고 있습니다. 일부 SPA 기반 프로젝트는 마이그레이션, 신규 도메인 서비스에 대해선 Next.js로 프로젝트를 만들어 AWS ECS에 서버 런타임을 띄워 운영하고 있습니다.
SPA(CSR) 프로젝트의 Router에서 Next.js의 파일 시스템 기반 라우팅으로 이관하는 중 기존에 정의된 규칙이 변경되어야 하는 상황이 생겼는데 그 중 하나가 바로 한글 pathname입니다.
예를 들어 OO은행대출서비스/페이지명/하위세그먼트 등 한글 그대로 하위 경로가 되기 때문에 PV 이벤트 트래킹, 지표 추적 등을 위한 Sementic Segmentation에 URL을 그대로 사용하고 있었습니다.
물론 한글 경로를 사용하면서 아래의 편의성을 얻을 수 있었습니다.
하지만 언제나 Trade-off입니다.
Next.js 환경으로 이관하게 되면서 Next.js에선 한글명 라우트가 지원되지 않아 별도의 주소 맵핑을 해주는 등의 조치가 필요했고 일반적인 방법으론 작동되지 않았습니다. 또한 URL 세그먼트의 한글 표기가 이중 인코딩 등의 문제로 이어지는 등 아래와 같이 관리 차원의 문제도 무시할 수 없었습니다.
한글 경로명으로 인한 관리상의 문제
정리하자면 문제를 인식하게 된 배경은 아래와 같습니다.
위 배경으로부터 아래의 문제가 파생되기 시작했습니다.
어떻게 일관된 규칙을 유지할 수 있을까요?
개발 조직 내 일관된 규칙을 어떻게 유지할 수 있을까?
제가 생각한 대안은 편의를 제공하는 도구가 자연스럽게 규칙을 지킬 수 있도록 유도하는 것이었습니다. 개발 편의를 위해 미리 규칙이 규정된 도구를 이용하다보면 자연스럽게 모든 자원이 일관된 규칙으로서 관리될 수 있다는 판단이었습니다.
페이지별 메타 데이터를 담은 pathmap을 자동으로 만들자
제일 먼저 Next.js가 파일시스템 기반으로 라우트를 구성해주는 것에 착안하여 파일 시스템의 하위 구조를 읽어 패스맵(Pathmap)으로 변환하는 도구를 만드는 것이 좋을 것 같다고 생각되었습니다.
처음엔 Next.js에서 빌드할때 TTY에 빌드 artifact에서 라우트 구성을 브리프하는 시퀀스가 있는데 여기서 출력되는 라우트를 이용하면 어떨지 생각해 보았습니다.
모노레포 관리 툴로 NX를 사용하고 있으니, Build Cache에 나타나는 Terminal Output의 문자열을 파싱해서 사용하면 되지 않을까 생각했습니다.
하지만 리모트 환경(배포 서버)에서 프로젝트를 빌드하는 타이밍에 pathmap을 만들고자 한 것은 의도와 다릅니다. 그렇다고 pathmap을 만들기 위해 개발자의 로컬 머신에서 별도의 build를 수행하는 것은 비효율적입니다.
다행히 Next.js는 파일 시스템 기반 라우트를 지원하기 때문에 pages 폴더를 재귀적으로 탐색하며 페이지를 구성하는 파일을 파싱하고, 루트 하위의 세그먼트를 딕셔너리 형태로 구성하면 원하는 형태의 pathmap을 자동으로 만들 수 있다고 생각했습니다.
Next.js 13버전부터 app directory라는 피쳐가 나와 페이지를 구성하는 컴포넌트들을 역할별로 colocation 시키는 방식을 지원하기 시작했지만, 저희 서비스에 본격적으로 도입하진 않았기 때문에 당장은 page extension으로 라우트를 구분하였습니다.\
그렇게 만들어진 next-pathmap 라이브러리
간단하게 생각대로 잘 되는지 실험해보기 위해 next-pathmap이라는 라이브러리를 만들어 보았습니다.
간단하게 설명하자면, 미리 정의해둔 config 파일의 데이터 모델, 또는 CLI 인터페이스 입력 시퀀스를 통해 Next.js의 모든 페이지 구조를 JSON으로 전환할 수 있는 라이브러리입니다.
위와 같이 프로젝트 루트에 config 파일을 미리 설정할 수도 있고
npx로 CLI 입력을 통해 pathmap을 생성할 수도 있습니다.
라이브러리 실행을 통해 아래와 같은 JSON 데이터를 얻을 수 있습니다.
저는 페이지의 메타 정보를 담기 위해 만들었기 떄문에 페이지 별칭, 카테고리, PV 트래킹 여부 등을 담았지만 이외에도 페이지마다 다양한 프로퍼티를 넣을 수 있어서 그 용도는 사용자가 활용하기 나름입니다.
그럼 어떤 생각으로, 어떻게 만들었는지 정리한 내용을 공유드립니다.
작동 순서
라이브러리의 의존성
파일 시스템의 구조를 읽어 패스맵으로 만드는 것은 아래 라이브러리들을 사용하여 간단히 해결할 수 있었습니다.
glob 패턴을 사용해서 특정 디렉토리 내 하위 디렉토리, 파일들을 파싱해주는 라이브러리입니다.
대화형 프롬프트 인터페이스를 쉽게 만들 수 있도록 돕는 라이브러리입니다.
TTY 환경에서 stdin/stdout으로 특정 옵션을 선택하거나 값을 입력하는 등 프로젝트 기본 설정을 위한 설정값 입력 프로세스에 사용되었습니다.
완성된 패스맵 JSON을 보기 좋게 formatting 해주는 라이브러리입니다.
라이브러리를 설정(configure)하는 두 가지 방법
pathmap은 두 가지 방법으로 configuration을 설정할 수 있습니다.
처음엔 CLI 인터페이스로 설정을 직접 입력하는 것을 생각했지만, 매번 경로가 수정될 때마다 설정을 입력하는 것이 번거롭기 때문에 흔히 사용하는 .confg.js 설정 파일을 루트 경로에 명시하는 것도 옵션으로 제공했습니다.
JS 개발 환경에서 써드파티 라이브러리를 사용할때 흔히 *.config.js의 포맷을 사용하는 것이 FE 개발자들에게 익숙하고, 프리셋이 있는 편이 더 편하다고 생각하여 next.config.js를 참조하여 만들어보았습니다.
1. *.config.js 설정 파일로 Configuration을 참조하는 방법
JSDoc Annotations
JSDoc 어노테이션을 사용하면 Config 객체에 어떤 프로퍼티를 입력해야 하는지 IDE에서 AutoSuggestion을 보여줍니다.
/** @type {import('path').member} */다른 파일에서 사용하고 있는 타입들은 import 선언을 통하여 가져올 수 있습니다. 이 구문은 TypeScript에 따라 다르며 JSDoc 표준과 다릅니다.
개별 프로퍼티에 대한 설명을 JSDoc으로 명시하면 특정 프로퍼티가 어떤 역할을 할 수 있는지 사용자가 확인할 수 있습니다.
JSDocs에 대한 더 자세한 내용은 링크를 참조해주세요.
2. 인터랙티브 프롬프트를 통해 Configuration을 입력하는 방법
Interactive Command Line Interface
패키지 등을 설치할때 CLI에서 값을 입력하거나 옵션을 선택하는 등의 인터랙션을 통해 셋업을 전달하는 방식을 경험해 보셨을 겁니다.
처음엔 CLI로 pathmap을 만들기 위한 몇가지 설정을 개발자가 입력할 수 있게 하고 싶었습니다.
이는 앞서 언급한 Inquirer 라이브러리를 사용하면 매우 간단하게 대화형 인터랙션을 구성할 수 있습니다.
inquirer를 사용해서 대화형 인터랙션이 작동하는 모습을 간단하게 도식화하면 아래와 같습니다.
https://github.com/SBoudrias/Inquirer.js
inquirer에 대한 자세한 내용은 inquirer 레포에서 확인하세요.
NPX 명령은 어떻게 작동하는가?
npm 의존성을 사용해본 분이시라면 npx 명령으로 특정 패키지의 바이너리를 작동시켜본 경험이 있으실 겁니다.
npx란 Node Package Execute의 약어로써 NPM 패키지를 프로젝트에 설치하지 않고 즉시 실행해주는 executer입니다. 마치 윈도우 브라우저에서 파일을 다운로드 할때 저장 또는 열기에서 열기를 선택해서 다운로드가 끝나자마자 즉시 실행하는 것과 개념적으론 비슷합니다.
그렇다면 제가 만든 라이브러리를 npx 명령으로 작동되게 하려면 아래의 것들이 필요합니다.
NPX가 작동하는 시나리오들
package.json
package.json이 존재하는 루트 디렉토리에 bin/scripts.js 경로와 파일이 존재한다는 가정 하에 아래와 같이 작성합니다.
{"name": "next-pathmap",
"bin": "./bin/script.js"
}
위와 같이 작성하면 npx를 통해 실행하고자 할때 bin 디렉토리의 파일을 실행 가능한 스크립트로 보고 작동시키게 됩니다.
인터프리터 디렉티브 #!/usr/bin/env node
쉘 스크립트를 다뤄보신 분들은 쉬뱅(Shebang — 욕 아닙니다)이라고 부르는 디렉티브를 아실겁니다.
이것은 유닉스 계열(Unix-like) 플랫폼에서 프로그램 로더에게 해당 문서의 문자열들이 실행 가능한(executable) 스크립트이며 어떤 인터프리터를 사용해야 하는지 바이너리 경로를 알려주는 표식의 역할을 합니다. 그렇다면 자바스크립트(Node.js 런타임)는 어떻게 디렉티브를 사용해서 실행시킬 수 있을까요?
인터프리터의 위치를 절대 경로로 표시하는 대신 /usr/bin/env 뒤에 인터프리터를 명시하는 목적은, POSIX 시스템마다 인터프리터의 경로가 달라질 수 있기 때문에 절대 경로 대신 $PATH에 지정된 해당 인터프리터의 디렉토리를 찾아 실행시키기 위함입니다.
빌드 타임에 컴파일된 index.js의 pathmap 프로그램을 호출하여 node 런타임에서 실행시켜주는 역할을 합니다.
런타임에서 라이브러리의 프로그램이 어떤 기능을 수행하는지는 아래 링크를 통해 소스 코드를 참조하시기 바랍니다.
https://github.com/wonkooklee/next-pathmap?search=1
프로젝트에 next-pathmap 셋업하기
1. config 파일 셋업
우선 프로젝트 루트 레벨에 프로젝트에 맞는 설정 파일을 생성합니다.
2. script 설정
편하게 스크립트를 사용하기 위해 워크스페이스의 package.json에 명령을 미리 등록합니다.
pnpm을 사용하기 때문에 npx 대신 pnpm dlx (executable 실행 명령)을 사용합니다.
shell 환경에서 next-pathmap을 명령어로 인식할 수 있도록 글로벌 설치 또는 로컬 node_modules의 bin 폴더에 있는지 확인합니다.
{"scripts": {
"gen:pathmap": "pnpm dlx next-pathmap"
},
}
3. next-pathmap 실행
명령을 실행하면 npm registry 또는 로컬 binary 디렉토리에서 executable을 가져와 실행시킵니다.
자세히 보면 프로그램이 pathmap.config.js has been detected를 출력하는 것을 확인할 수 있습니다. config를 잘 찾아서 정상적으로 참조하였다는 피드백입니다.
몇 초의 파싱 과정이 끝나면 SUCCESS 메시지가 출력되고 명령 실행 위치(pwd) 기준으로 어디에 파일이 생성되었는지 OUTPUT을 통해 알려줍니다.
만약 파싱 과정에서 문제가 발생한다면 process.exit의 각 예외처리된 코드를 통해 문제를 알려줍니다.
지정된 위치(src/pathmap/pathmap.json)에 패스맵 파일이 생성되었습니다.
이제 이 파일을 활용해서 URL 루트에 맞는 이벤트 명을 맵핑하거나 여러 메타 데이터를 담을 수 있습니다.
4. githook 연동
git-hook 관련 솔루션은 많지만 husky로 간단한 예시를 보여드립니다.
.husky/pre-push
push 과정에서 페이지 메타 정보를 누락시키진 않았는지 앞선 next-pathmap의 프로세스 종료 코드를 참조하여 성공/실패 여부를 판가름합니다.
pathmap 생성에 성공하고, alias 등 자원 관리상 작업자가 지정해주어야 하는 정보의 누락이 없을 경우 코드 저장소에 커밋을 올릴 수 있게 됩니다.
개발자의 고민
개발자로서 일을 하다보면 ‘어떻게 만들것인가’보다 ‘어떻게 관리할 것인가’에 대해 훨씬 많은 고민을 하게 됩니다.
이번엔 블로그 글로 적기 좋은 소재로써 URL과 이벤트명 컨벤션에 대한 라이브러리 구현기를 다뤘지만, 평소에도 용이하고 일관적인 유지 관리 방법에 대해 많은 시도와 고민을 하고 있습니다.
서비스의 일환으로 사용되는 페이지와 에셋, 소스 코드 등 모든 것은 관리의 대상이고 조직의 자원이기 때문에 만들기만 하는 것보단 잘 활용되고 있는지, 누락이나 보완해야 하는 사항은 없는지, 어떻게 하면 더 손쉽게 관리가 가능할지 고민한다면 더 좋은 개발자로 성장할 수 있는 계기가 되지 않을까 생각해봅니다.