본문으로 건너뛰기

putree 17. origin별 세션 전달과 명령 이름 바로잡기

·12 min read·17 / 20

서브도메인에 있는 페이지만 로그인 화면으로 찍혔다. 우회로 넘길 문제가 아니어서 뿌리를 정면으로 봤다.

그 쿠키는 애초에 캡처 요청에 실릴 수 없었다

맵이 캡처를 요청하면 브라우저는 그 요청이 향하는 호스트의 쿠키만 싣는다. cms.localhost의 세션은 host-only 쿠키다. 발급한 호스트로 가는 요청에만 실린다는 뜻이다. 그런데 캡처 요청은 맵이 열려 있는 호스트로 나가니, 그 세션이 캡처 경로로 전달될 방법이 원리적으로 없다.

우회할 구멍을 찾는 대신 그 요청을 실제로 받는 쪽을 봤다. 캡처 요청은 결국 앱에 도착하고, 앱은 자기 origin의 쿠키를 안다. 그러니 앱이 알려주면 된다.

putree/next 진입점으로 sharePutreeSession, isPutreeCapture, putreeMiddleware를 열었다. 셋 다 Edge 런타임(Node API 없이 도는 경량 실행 환경)에서 안전하게 돌고, dev에서만 동작한다.

POST /api/session/origin이 origin별 쿠키를 받아 server/origin-sessions.ts의 메모리에만 보관한다. 파일로 남기면 로그인 세션이 저장소에 눌러앉는다. 캡처 컨텍스트는 x-putree-capture 헤더에 세션 토큰을 실어 보내고, 앱은 dev 한정으로 로그인 가드를 건너뛴다. 로그인 없이 찍는 길이 이걸로 생겼다.

명령이 네 개면 사람은 그 앞에서 멈춘다

목표는 계속 같다. 비개발자가 코딩 없이 설치부터 페이지 설정까지 갈 수 있어야 한다. 그런데 외워야 할 명령이 네 개면 사람은 시작하기 전에 멈춘다.

그래서 putree init 하나가 락파일로 패키지 매니저를 판정해 설치하고, 스캔을 돌리고, <PageMap>을 마운트하고, middleware.ts까지 만들도록 묶었다. 멱등하게 만들었다. 여러 번 실행해도 결과가 같다. 넣을 자리를 확신할 수 없으면 파일을 고치지 않고 붙여야 할 코드를 출력한다. putree devia.config.ts가 없고 대화형이면 그 자리에서 init을 돌린다.

scan 구현은 cli/scan.ts로 뺐다. init이 scan을 쓰는데 둘 다 index에 있으면 순환 import가 난다.

버튼을 달았다가 다시 걷어냈다

처음엔 로그인 리다이렉트 카드에 "로그인 없이 찍기" 버튼을 달았다. 터미널을 열지 않고도 dev 전용 통과 문을 켤 수 있어야 한다고 봤다. POST /api/setup/middlewareputree init과 같은 ensurePutreeMiddleware를 부르게 해서 두 경로가 갈라지지 않게 했다. 켠 뒤 자동 재캡처는 하지 않았다. dev 서버가 새 미들웨어를 물기 전이라 또 로그인 화면이 나오고, 그러면 안내 카드마저 사라진다.

그리고 그 버튼을 다시 지웠다. 버튼의 UX가 문제가 아니라 파일의 수명이 문제였다.

미들웨어가 없던 프로젝트에 파일이 하나 생기면 production에서도 매 요청 실행된다. 호출 단위로 과금하는 호스팅도 있다. 버튼 한 번으로 남의 프로덕션 비용 구조를 바꾸는 건 정직하지 않다. 필요한 팀이 putree middleware 명령을 한 번 치는 편이 낫다. init은 파일을 만들지 않고 안내만 하게 바꿨고, 되묻기(--yes/ask)도 지웠다. 카드는 명령 한 줄만 보여준다. 이름은 곧 putree mw로 줄였고 middleware는 별칭으로 남겼다.

지울 때 손댈 게 배선뿐이었던 건 버튼과 CLI가 ensurePutreeMiddleware를 공유하고 있었기 때문이다. dev 편의 기능이 production에 파일을 남긴다면 그건 이미 dev 기능이 아니다.

0.10.0을 내고 4분 만에 되돌렸다

putree/next 진입점과 init·mw 명령을 묶어 0.10.0을 냈다. 그리고 A프로젝트에서 putree mw를 돌리자 모든 페이지가 404가 됐다. Next 16을 쓰는 프로젝트였다. 4분 만에 0.9.24로 되돌렸다. 그렇게 빨랐던 건 버전 범프가 CHANGELOG와 package.json 두 줄로 격리돼 있었기 때문이다.

원인을 알고 나서 제일 아팠다. Next 16에서 middleware.tsproxy.ts로 이름이 바뀌었다. 내보내는 함수 이름도 proxy다. 우리 코드는 옛 이름만 찾아보고 "미들웨어가 없다"고 판정한 다음 새 middleware.ts를 만들었다. 그 프로젝트에서 라우팅·리다이렉트·세션을 책임지던 proxy.ts 대신 우리가 만든 파일이 실행됐다.

flowchart TD
    A["middleware.ts만 탐색"] --> B["미들웨어 없음으로 판정"]
    B --> C["새 middleware.ts 생성·실행"]
    C --> D["기존 proxy.ts는 실행되지 않음"]
    D --> E["라우팅·세션 처리 사라짐"]
    E --> F["모든 페이지 404"]

고친 방식은 단순하다. 파일 이름 proxy·middleware, 확장자 .ts·.js·.mjs, 위치 루트·src/를 조합한 열두 자리를 전부 확인하고 나서야 없다고 판단한다. 새로 만들 때 Next 16 이상이면 proxy.ts로 만든다.

"파일이 없다"는 판정은 이름 하나에 걸려 있다. 프레임워크 규약 파일을 다루면서 탐색 범위를 좁게 잡으면 "없음"이 곧 "덮어씀"이 된다. 그 파일이 라우팅이나 인증처럼 앱의 뼈대를 쥐고 있으면 실패가 조용하지 않고 전면적이다.

이름이 결과가 아니라 만드는 물건을 가리키고 있었다

코드는 그날 밤에 고쳤다. 그런데 mw라는 이름이 잘못된 전제를 그대로 들고 있다는 걸 알아챈 건 자정을 넘겨서였다.

만드는 파일이 Next 버전에 따라 middleware.ts거나 proxy.ts다. 그러면 파일 이름을 딴 명령 이름은 절반의 사용자에게 늘 어긋난다. Next 16을 쓰는 사람에게 "putree mw를 실행하라"고 안내해도 그 사람 프로젝트에는 mw라는 것이 없다.

이름은 만드는 물건이 아니라 하는 일을 가리켜야 한다. 이 명령이 하는 일은 캡처가 로그인 가드를 통과하게 열어주는 것이다. 그래서 putree unlock으로 지었다. mwmiddleware는 별칭으로 남겼다. 몇 시간 전 안내를 보고 이미 손에 익힌 사람이 있을 수 있다. CLI와 README, CHANGELOG, 카드 안내 문구를 같이 바꿨다. 카드가 알려주는 한 줄 명령도 이 이름이다.

이름을 이틀 사이에 두 번 바꿨다. middleware에서 mw로, 다시 unlock으로. 그래도 아무도 깨지지 않는 건 옛 이름을 다 받아주기 때문이다. 이름 고치기를 망설이게 되는 이유가 대개 호환인데, 별칭 한 줄이면 그 비용이 사라진다.

네 번 막히고 네 번 고쳤다

문제원인조치
서브도메인 페이지만 로그인 화면으로 찍힘host-only 쿠키는 발급한 호스트로 가는 요청에만 실린다. 캡처 요청은 맵의 호스트로 가서 cms.localhost 세션이 붙지 않는다앱이 putree/next 미들웨어로 origin별 쿠키를 넘겨주게 함
맵의 "로그인 없이 찍기"가 프로덕션 비용을 바꿈없던 middleware.ts가 생기면 production 전 요청에서 실행된다버튼과 엔드포인트를 지우고 CLI 명령으로 옮김
putree mw 실행 후 앱의 모든 페이지가 404Next 16은 middleware.tsproxy.ts로 바뀌었다. 옛 이름만 찾아 없다고 판정하고 새 파일을 만들어 진짜 프록시를 덮었다열두 자리 전수 탐색, Next 16이면 proxy.ts로 생성
명령 이름이 절반의 사용자에게 어긋남mw가 "middleware.ts를 만든다"는 구현을 이름에 실었다putree unlock으로 이름을 바꾸고 옛 이름은 별칭으로 유지

미들웨어 버튼을 만들었다가 지운 왕복이 낭비처럼 보이지만, 지우는 판단의 근거인 프로덕션 실행 비용은 만들어 보기 전에는 떠오르지 않았다. 다만 그 판단을 하기 전에 릴리즈를 먼저 냈고, 그게 404 사고로 이어졌다. 남의 프로젝트에 파일을 만드는 기능은 릴리즈 전에 프레임워크 버전별 규약을 한 번 훑어야 했다. 사고 뒤에 코드만 고치고 끝낼 뻔한 것도 마찬가지다. 코드 말고 이름과 문서에 남은 낡은 전제까지 훑어야 한다.

남은 건 unlock을 적용한 0.10.x 재릴리즈, A프로젝트 어드민에서 putree unlock 실기동 확인, putree init을 실제 비개발자 손에 한 번 돌려보는 것이다. 서버 연결 실패(ERR_CONNECTION_REFUSED)가 무더기로 찍혀 콘솔이 시끄러운 것도 정리해야 한다.