robots.txt 정적 전환 + middleware deep link 우회 + MY 탭 내 계정 카드 — POP-SPOT v2.15 (~v2.15.3)
v2.15.1 robots.txt 동적→정적 / v2.15.2 ?tab=MY 직접 진입 시 인트로로 튕기던 middleware 통과 / v2.15.3 MY 탭 상단에 회원 이름·이메일·프로필 사진 카드 추가. 작은 운영 보강 3 건 묶음.
v2.14 이후 큰 기능 도입 없이 운영 마찰을 줄이는 자잘한 세 가지 패치. robots.txt 응답 안정화, 딥링크가 인트로에서 튀던 버그, MY 탭 첫 화면에 내 계정 정보 노출.
이 글에서 다루는 것
모르는 단어 한 줄로
| 용어 | 한 줄 설명 |
|---|---|
| robots.txt | 검색 엔진 크롤러에게 "어디는 들어와도 되고 어디는 들어오지 마" 알려주는 표준 파일 |
| 동적 라우트 | 요청이 올 때마다 서버가 응답을 계산해서 돌려주는 방식 |
| middleware (Next.js) | 모든 요청이 페이지에 도달하기 전에 통과하는 가운데 단계. 로그인 체크·리다이렉트에 쓰임 |
| deep link | 특정 화면으로 바로 진입하게 해주는 URL. 예) <code>?tab=MY</code> |
무엇이 바뀌었나
| 버전 | 변경 |
|---|---|
| v2.15.1 | robots.txt 동적 → 정적 파일 |
| v2.15.2 | middleware 가 <code>?tab=…</code> 쿼리를 가진 진입을 그대로 통과시킴 |
| v2.15.3 | MY 탭 상단에 "내 계정" 카드 — 이름·이메일·프로필 사진 |
v2.15.1 — robots.txt 정적 전환
코드와 해석
파일: popspot-frontend/app/robots.ts (동적, 삭제 대상)
// v2.15.0 까지 — app/robots.ts (동적 라우트)
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: [{ userAgent: '*', allow: '/', disallow: ['/api/'] }],
sitemap: 'https://popspot.co.kr/sitemap.xml',
};
}해석 — app/robots.ts 는 Next.js 가 매 요청마다 객체를 직렬화해서 /robots.txt 응답으로 내보내는 라우트 핸들러. 결과는 정적인데 서버 단계 한 번이 끼어 있다. 크롤러가 자주 때리면 응답 지연 가능성이 있고, 일부 환경(Vercel ISR 캐시 miss)에서 한 번씩 404 가 났다.
# v2.15.1 — public/robots.txt (정적 파일)
User-agent: *
Allow: /
Disallow: /api/
Disallow: /music/passport
Sitemap: https://popspot.co.kr/sitemap.xml해석 — public/ 안에 둔 파일은 빌드 결과물에 그대로 들어가서 정적 자산으로 서빙된다. Next.js / Vercel 둘 다 정적 파일은 CDN 캐시 친화적이라 응답 지연 0, 404 가능성 0.
트레이드오프
핵심 파일
v2.15.2 — middleware 딥링크 우회
사건 흐름
사용자가 외부에서 https://popspot.co.kr/?tab=MY 링크를 누름 → 의도는 메인의 MY 탭으로 진입 → 그런데 인트로 페이지로 강제 리다이렉트 → 인트로 끝까지 보고 다시 메인 가서 MY 탭 누르는 두 번 일.
원인
middleware.ts 가 "메인 첫 방문이면 인트로 거치자" 룰만 단순하게 적용하느라 쿼리 파라미터가 있는 경우 를 구별 안 함.
파일: popspot-frontend/middleware.ts (Next.js 전역 미들웨어)
// v2.15.1 까지 — middleware.ts. pathname 만 보고 query 은 무시
export function middleware(req: NextRequest) {
const visited = req.cookies.get('entered');
// 위: 인트로 한 번이라도 거친 사용자는 이 쿼키가 채워짐
if (!visited && req.nextUrl.pathname === '/') {
// 위: 첫 방문 + 루트 경로 → 인트로 강제 이동
// 문제: ?tab=MY 같은 쿼리가 있어도 pathname 은 여전히 '/' — 의도와 무관하게 인트로로 튔김
return NextResponse.redirect(new URL('/intro', req.url));
}
}해석
수정
파일: 같은 middleware.ts (v2.15.2 수정 후)
// v2.15.2 — 딥링크 화이트리스트 추가. 의도 명확한 진입은 인트로 건너뛰기
const DEEP_LINK_KEYS = ['tab', 'open', 'q', 'region', 'period', 'category'];
// ^^^^^ ^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 탭 모달 검색 BROWSE 필터 (지역·기간·카테고리)
export function middleware(req: NextRequest) {
const visited = req.cookies.get('entered');
const hasDeepLink = DEEP_LINK_KEYS.some(
(k) => req.nextUrl.searchParams.has(k)
// ^^^^ 이 키가 ?...= 안에 있는지만 체크 (값은 안 본다)
);
// 위: some() — 하나라도 맞으면 true. 어떤 딥링크라도 있으면 인트로 건너뛰기
if (!visited && req.nextUrl.pathname === '/' && !hasDeepLink) {
// ^^^^^^^^^^^^ 딥링크 없을 때만 인트로 강제 이동
return NextResponse.redirect(new URL('/intro', req.url));
}
}
// 결과: ?tab=MY 있으면 인트로 우회 → 마이페이지 1클릭 진입해석
결과: ?tab=MY 진입 → 인트로 우회 → 메인 + MY 탭이 한 번에 떴다.
비하인드
이런 종류의 버그는 보통 사용자가 카카오톡으로 내 사이트 링크를 공유했을 때 발견된다. 카카오톡이 OpenGraph 미리보기를 위해 URL 을 한 번 fetch 하는데 그게 인트로로 튕기면 미리보기가 인트로 화면. 메인 화면이 미리보기로 안 나오니 클릭률이 떨어진다. v2.20.3 의 봇 우회와는 다른 방향(사용자 의도 우회)이지만 결국 비슷한 결의 문제.
핵심 파일
v2.15.3 — MY 탭 "내 계정" 카드
문제
MY 탭에는 위시·코스·스탬프 같은 "내 활동" 카드는 있는데, 내 계정 정보 (이름·이메일·프로필 사진) 가 한 군데도 안 보였다. 사용자가 "이 사이트에 내 정보가 뭘로 저장되어 있지?" 를 확인하려면 "프로필 수정" 같은 별도 페이지로 들어가야 함.
수정 — 작은 카드 한 장
파일: popspot-frontend/src/app/page.tsx 의 MY 탭 컴포넌트 부분 (MyAccountCard 서브함수)
// v2.15.3 — MyPage.tsx 상단에 계정 정보 카드 추가
function MyAccountCard({ user }: { user: User }) {
return (
<section className="rounded-2xl border border-zinc-200 p-4 mb-4
flex items-center gap-3 bg-white">
<Avatar src={user.profileImageUrl} name={user.name} size={48} />
<div className="flex-1 min-w-0">
<p className="font-medium text-sm truncate">{user.name}</p>
<p className="text-xs text-zinc-500 truncate">{user.email}</p>
</div>
<Link href="/profile/edit" className="text-xs text-lime-600">
편집
</Link>
</section>
);
}해석
디자인 결정
로그아웃 버튼을 카드 안에 넣고 싶은 유혹이 있었지만 분리. 로그아웃은 "실수 클릭 위험" 이 있는 동작이라 다른 영역(헤더 또는 설정)에 두는 게 표준. MY 카드는 "보여주는" 역할만.