[구글 SEO 15] 페이지네이션이 만드는 무한 페이지 봇 함정

게시판 목록은 글이 쌓이면 여러 페이지로 나뉜다. 1페이지, 2페이지, 100페이지 하는 식이다. 사람에겐 당연한 구조인데, 이 페이지네이션이 잘못 설계되면 봇에게는 끝없이 이어지는 미로가 된다. 심하면 봇이 존재하지도 않는 페이지를 무한히 파고들며 크롤 예산을 통째로 태운다. 이번 편은 페이지네이션이 만드는 함정과 그 해법이다.


앞서 파라미터 중복 URL을 다뤘는데, 페이지네이션은 그와 비슷하면서도 다르다. page 파라미터는 각기 다른 글을 담고 있어서 무작정 합칠 수 없기 때문이다. 그래서 별도의 전략이 필요하다.


1. 페이지네이션은 왜 특별한가

정렬이나 필터 파라미터는 같은 글의 다른 보기라 중복으로 묶을 수 있었다. 하지만 페이지네이션은 다르다. 2페이지에는 1페이지에 없는 글이 담겨 있다. 즉 뒤쪽 페이지는 그 자체로 고유한 글 목록을 가진 발견 통로다. 이걸 중복이라고 canonical로 묶어 1페이지로 몰아버리면, 뒤쪽 글들이 발견 경로를 잃는다.


그래서 페이지네이션의 원칙은 중복 제거가 아니라 발견 통로 유지다. 봇이 2페이지, 3페이지를 타고 들어가 안쪽 글까지 발견할 수 있어야 한다. 특히 사이트맵이 부실하거나 없다면, 페이지네이션이 안쪽 글을 발견하는 유일한 길이 되기도 한다.


동시에 페이지네이션 페이지 자체는 색인 가치가 낮다. 2페이지 목록이 검색 결과에 뜰 일은 거의 없다. 그래서 발견 통로로는 살리되 색인 대상으로는 힘을 빼는, 미묘한 균형이 필요하다. 이 균형을 어떻게 잡느냐가 이번 편의 핵심이다.


용어를 정리하면 여기서 말하는 무한 페이지는 실제로 무한한 게 아니라, 경계가 없어서 봇이 끝을 모른 채 계속 다음을 요청하는 상태다. 사람은 마지막 페이지에서 다음 버튼이 없으면 멈추지만, 봇은 URL 규칙만 알면 페이지 번호를 계속 올려가며 요청할 수 있다. 그래서 명시적인 끝을 서버가 알려줘야 한다.


실제 사례를 하나 더 들면, 검색으로 유입될 가치가 없는 정렬 조합까지 페이지네이션과 뒤섞이면 조합이 폭발한다. 100페이지짜리 목록에 정렬 세 종류가 곱해지면 300개의 목록 URL이 나온다. 그래서 페이지네이션을 다룰 때는 앞서 정리한 정렬·필터 파라미터 정책과 반드시 함께 설계해야 조합 폭발을 막는다.


2. 무한 페이지 함정

가장 위험한 건 존재하지 않는 페이지까지 봇이 요청하는 상황이다. 예컨대 마지막이 100페이지인데 101페이지, 102페이지를 요청해도 서버가 빈 목록을 200 OK로 돌려주면, 봇은 다음 페이지가 계속 있는 줄 알고 무한히 파고든다. 페이지 번호만 바꾼 URL이 무한히 생성되는 것이다.


내가 실제로 크롤 통계에서 이 흔적을 봤다. 실제 글은 수백 개인데 봇이 요청하는 목록 페이지 번호가 비정상적으로 컸다. 존재하지 않는 페이지를 향한 이 헛걸음이 크롤 예산을 갉아먹고 있었다. 앞서 다룬 D1 같은 데이터베이스 관점에서도, 이런 무한 페이지 요청은 불필요한 쿼리 부하로 이어진다.


원인은 범위를 벗어난 페이지 요청에 서버가 정직하게 없다고 응답하지 않는 데 있다. 빈 결과를 200으로 주는 건 앞서 다룬 소프트 404의 목록 버전이다. 봇에게 여기 아무것도 없지만 정상이야라고 잘못된 신호를 주는 것이다.


이 함정은 특히 자동으로 페이지 링크를 생성하는 구조에서 잘 생긴다. 다음 페이지 링크를 조건 없이 항상 만들어버리면, 마지막 페이지에도 다음 링크가 붙어 봇을 존재하지 않는 페이지로 안내한다. 그래서 링크 생성 자체에 마지막 페이지에선 다음 링크를 안 만든다는 조건을 넣는 게 1차 방어다.


3. 범위를 벗어난 페이지는 404로

해법의 첫걸음은 명확하다. 존재하는 페이지 범위를 벗어난 요청에는 404를 돌려주는 것이다. 100페이지가 마지막이면 101페이지 요청에는 404를 준다. 그러면 봇은 여기가 끝이라는 걸 알고 더 파고들지 않는다. 무한 미로의 출구를 막는 셈이다.


구현할 때는 요청된 페이지 번호가 전체 페이지 수를 넘는지 서버에서 검사하면 된다. 전체 글 수를 페이지당 개수로 나눈 최대 페이지를 계산해두고, 그걸 넘는 요청은 404 상태 코드로 응답한다. 화면에는 없는 페이지라고 안내하되 HTTP 상태는 반드시 404여야 한다. 소프트 404를 반복하지 않기 위해서다.


동적으로 페이지 상한을 정할 때는 데이터 규모에 맞춰 유연하게 계산하는 게 좋다. 글이 늘면 상한도 자연히 커지게 해두면, 정상 범위는 열어두고 그 밖만 막는 정확한 경계가 유지된다. 이 경계 하나로 무한 페이지 크롤이 딱 멈춘다.


404를 줄 때 함께 챙길 것은 사용자 경험이다. 사람이 실수로 범위 밖 페이지를 열었을 때 딱딱한 404 화면만 보여주기보다, 없는 페이지임을 알리면서 첫 페이지나 목록으로 돌아갈 링크를 함께 주는 게 친절하다. 상태 코드는 404로 정확히 주되 화면은 막다른 길이 아니게 만드는 것이다.


서버에서 페이지 상한을 계산할 때 캐싱을 고려하면 좋다. 전체 글 수를 매 요청마다 세면 부하가 되니, 그 값을 적당히 캐시해두고 페이지 범위 검사에 쓰는 식이다. 무한 페이지를 막자고 넣은 검사가 오히려 서버 부하를 만들면 본말이 전도된다. 경계 검사는 가볍게 도는 방식으로 구현하는 게 좋다.


4. 페이지네이션 링크 설계

발견 통로를 살리려면 페이지네이션 링크가 실제 a 태그로 크롤 가능해야 한다. 자바스크립트 onclick으로만 페이지를 넘기고 진짜 링크가 없으면, 봇은 2페이지로 갈 방법이 없어 안쪽 글을 영영 못 본다. 다음 페이지 버튼과 페이지 번호는 반드시 실제 href를 가진 링크여야 한다.


또 하나, 이 페이지네이션 URL은 앞서 다룬 파라미터 정리 정책과 충돌하지 않게 설계해야 한다. 필터나 정렬은 프래그먼트로 숨기더라도 페이지 이동은 크롤 가능한 형태로 남기는 식이다. 나는 필터와 정렬은 해시로, 페이지 이동은 봇이 따라갈 수 있는 경로로 분리해서 발견 통로만 살렸다.


과거에 rel=prev와 rel=next로 페이지 순서를 알려주는 방식이 권장되던 시절이 있었다. 지금 구글은 이 신호를 공식적으로는 쓰지 않는다고 밝혔지만, 페이지 간 링크가 서로 잘 연결돼 있으면 봇이 순서를 파악하는 데 여전히 도움이 된다. 핵심은 태그보다 링크 자체의 연결성이다.


링크 설계에서 하나 더, 페이지 번호를 너무 많이 한 번에 노출하지 않는 것도 방법이다. 1번부터 100번까지 모든 번호를 매 페이지에 깔면 그만큼 링크가 많아진다. 앞뒤 몇 개와 처음, 끝 정도만 노출하고 나머지는 생략하는 흔한 페이지네이션 UI가, 사람에게도 봇에게도 적당한 밀도를 준다.


5. 색인 힘 빼기와 대안

페이지네이션 페이지 자체는 검색에 뜰 필요가 없으니 색인 힘을 뺀다. 다만 앞서 강조했듯 noindex를 쓰더라도 크롤은 열어둬야 봇이 그 페이지를 통해 안쪽 글로 넘어간다. noindex는 이 목록 페이지를 색인하지 말라는 것이지, 여기 링크를 따라가지 말라는 게 아니다. 이 둘을 구분해야 발견 통로가 안 끊긴다.


더 근본적인 대안은 사이트맵을 튼튼히 하는 것이다. 사이트맵이 모든 글을 잘 담고 있으면, 봇이 굳이 깊은 페이지네이션을 타지 않아도 안쪽 글을 발견한다. 그러면 페이지네이션은 사람을 위한 편의로 남고, 봇의 발견은 사이트맵이 책임지는 이상적인 분담이 된다. 나는 이 방향으로 갔다.


정리하면 페이지네이션은 발견 통로로는 살리고 색인 대상으로는 힘을 빼되, 존재하지 않는 페이지는 404로 막아 무한 미로를 끊는 것이다. 여기까지가 크롤과 온페이지의 기술적 토대다. 다음 편부터는 관점을 바꿔, 기술이 아니라 콘텐츠와 권위의 영역으로 들어간다. 첫 주제는 2026년 구글이 무엇을 가장 중요하게 보는가다.


색인 힘 빼기와 관련해 하나 강조하면, 목록 페이지를 검색에서 빼더라도 그 안의 개별 글은 각자의 실주소로 색인돼야 한다. 목록은 통로일 뿐 목적지는 개별 글이다. 그래서 목록 페이지의 색인 여부와 개별 글의 색인 여부를 헷갈리면 안 된다. 통로는 힘을 빼고 목적지는 살리는 게 원칙이다.


마지막으로 강조하면, 페이지네이션 문제는 사이트가 커질수록 심해진다. 글이 수천, 수만 개가 되면 목록 페이지도 그만큼 늘어난다. 그래서 초기에 이 구조를 제대로 잡아두면 나중에 편하고, 방치하면 규모와 함께 크롤 낭비도 커진다. 작을 때 잡아두는 게 언제나 싸게 먹힌다.