뉴스레터 링크의 utm_source를 없애려고 물음표 뒤를 전부 지웠습니다. 주소는 짧아졌지만 상품 번호와 언어, 검색어까지 사라졌습니다. 보기 좋은 URL과 같은 내용을 가리키는 URL은 다릅니다. 이번 글은 추적용으로 정한 이름만 제거하는 제한 규칙을 네 가상 주소에 적용하고, 검색 결과의 대표 주소 지정과 URL 정리 작업을 구분합니다.

매개변수 이름만 보고 역할을 추측하지 않습니다.

item=7은 상품을 고르고 lang=ko는 표시 언어를 바꿀 수 있습니다. page=2는 목록의 다음 부분을 보여 주고 q=paper는 검색 결과를 만듭니다. 반면 일부 utm 값은 유입 경로를 기록하는 목적으로 사용됩니다. 같은 사이트라도 기능이 다를 수 있으므로 의미를 모르는 이름을 일괄 삭제하지 않는 것이 출발점입니다.

운영자에게 매개변수별 역할을 확인하고 “내용 선택”, “표시 선택”, “세션·권한”, “추적”, “서명”, “미확인”처럼 구분합니다. 이름이 짧다고 본문 값이고 길다고 추적값인 것은 아닙니다. 분류표에는 예제 URL과 값이 있을 때·없을 때 어떤 화면이 나오는지 적습니다. 실제 계정 토큰을 예제에 사용하지 말고 공개해도 되는 합성 값으로 동작을 확인해야 합니다.

이번 규칙은 삭제 목록 세 개만 허용합니다.

코드는 utm_source, utm_medium, utm_campaign만 제거 대상으로 정했습니다. 나머지 이름은 보존하고 signature가 있으면 주소를 바꾸지 않은 채 보류합니다. 모든 utm 접두사를 지우는 범용 규칙도, 모든 웹사이트에 적용할 검색 최적화 도구도 아닙니다. 명시한 삭제 목록이 이 가상 사이트의 계약이라는 점을 먼저 밝힙니다.

URLSearchParams로 쿼리를 파싱하고 선택한 이름을 삭제한 뒤 URL 객체의 href를 기록합니다. 문자열에서 & 앞뒤를 정규식으로 잘라 내지 않아 구분자를 잘못 남길 가능성을 줄였습니다. 다만 파싱 후 재직렬화 과정에서 표현이 달라질 수 있습니다. 바이트 단위로 같은 주소가 필요한 상황에는 이 함수 자체를 적용하면 안 됩니다.

상품·검색·서명·공백 네 입력을 따로 봤습니다.

첫 입력은 상품 번호 7과 한국어 선택, 메일 유입 값을 함께 갖습니다. 실행 후 item=7과 lang=ko는 남고 utm_source만 없어졌습니다. 둘째는 검색어 paper, 2페이지, 캠페인 이름을 넣었고 q와 page는 유지됐습니다. 짧게 만든다는 이유로 상품이나 검색 결과를 바꾸지 않았는지 확인한 것입니다.

셋째는 tag=a와 tag=b라는 중복 이름, 합성 signature를 넣었습니다. 결과는 원문 그대로와 hold-signed-url입니다. 넷째는 q=a+b와 추적값을 넣었으며 파싱한 검색어의 공백 의미와 주소의 표현을 구분해서 읽어야 합니다. 실행 결과 JSON의 input과 output을 나란히 보면 어떤 값이 남았는지 확인할 수 있습니다. 실제 검색 서비스에 요청하지는 않았습니다.

같은 이름이 여러 번 나오면 하나의 값으로 바꾸지 않습니다.

태그 필터가 tag=a&tag=b처럼 두 값을 사용할 수 있습니다. 일반 객체로 옮기면서 뒤의 값으로 덮어쓰면 주소 정리 과정에서 선택 조건이 사라집니다. URLSearchParams의 get과 getAll이 다른 이유도 여기에 있습니다. 값 하나만 읽는 함수가 업무의 다중 선택을 이해한다고 볼 수는 없습니다.

순서의 의미도 사이트마다 확인해야 합니다. 값을 정렬하면 같은 내용이라고 가정할 수 있는 경우도 있지만, 우선순위나 서명 계산에 순서가 쓰이는 경우에는 결과가 달라질 수 있습니다. 이번 코드는 전체 매개변수를 정렬하지 않습니다. 일부러 바꾸지 않은 부분을 기록해야 “정규화됐으니 언제나 동등하다”는 오해를 줄일 수 있습니다.

입력별 결과를 편집 판단에 연결하기

다음 표의 결과는 제공 실행 파일에서 확인한 값이며, 해석과 다음 확인은 그 결과를 이용하는 편집 예시입니다. 추가 시험을 수행한 결과로 세지 않습니다.

입력·관찰실행 결과 또는 선택판단 근거다음 확인
상품 URLitem=7과 lang=ko를 남기고 utm_source만 지웠습니다.상품 선택과 언어 선택은 이 예제의 콘텐츠 조건입니다. 정리 전후를 비교하지 않고 물음표 뒤를 모두 없애면 이 선택을 잃습니다.같은 상품의 같은 언어 정보가 보이는지 실제 서비스에서 별도 확인합니다.
검색 URLq=paper와 page=2를 남겼습니다.검색어와 다음 페이지를 첫 페이지의 기본 화면으로 바꾸지 않았다는 문법 결과입니다. 검색 결과가 실제로 같은지는 네트워크를 열어 보지 않아 미확인입니다.페이지 번호가 있는 주소를 대표 주소에 합칠지 결정하기 전에 콘텐츠 차이를 비교합니다.
서명 URLtag 중복과 signature를 포함한 원문 전체를 보류했습니다.서명 값을 지우거나 다시 직렬화하지 않았습니다. 실제 인증 검증이 성공했다는 뜻도, 이 주소가 안전하게 공개 가능하다는 뜻도 아닙니다.발급 시스템에서 장기 공개 주소를 제공하는지 확인합니다.
공백 URLq=a+b의 검색 값과 추적 값 삭제를 분리해서 봅니다.플러스 기호를 일반적인 덧셈 기호로만 해석하면 폼 인코딩과 충돌합니다. 원시 표현과 파싱된 값을 나란히 남겨야 합니다.디코딩·인코딩을 여러 번 하지 않도록 어느 층에서 변환하는지 정합니다.

플러스·공백·퍼센트 인코딩은 문자열 장식이 아닙니다.

폼 URL 인코딩에서는 플러스 기호와 공백의 해석 규칙이 있습니다. URLSearchParams로 읽은 값과 원시 쿼리를 혼동하면 이미 인코딩된 값을 한 번 더 인코딩하거나, 진짜 플러스를 공백으로 바꿀 수 있습니다. 사용자에게 보여 주기 위한 디코딩과 서버에 보낼 주소의 직렬화는 목적이 다르므로 단계를 나눕니다.

검수 기록에는 원시 주소와 파싱한 이름·값, 최종 주소를 모두 남깁니다. 단, 민감한 값은 기록 전부터 제외해야 합니다. 입력과 출력의 글자가 다르다는 이유만으로 오류라고 결론 내리지 말고 각 값이 같은 의미인지, 서버가 어떤 표현을 요구하는지 봅니다. 이번 실험은 예약 도메인의 가상 주소를 대상으로 한 문법 처리입니다.

서명 주소는 뜻이 같아 보여도 다시 만들지 않습니다.

서명·만료 시각·접근 권한이 포함된 URL은 값이나 순서, 인코딩을 바꾸면 검증이 실패할 수 있습니다. 합성 signature가 들어 있는 셋째 주소를 보류한 것은 그런 주소의 바이트 계약을 알 수 없기 때문입니다. 실제 인증 정보를 검증하거나 서명 알고리즘을 시험한 결과가 아닙니다.

실제 자료 공유에 서명 링크가 필요하면 발급 시스템의 안내와 공개 가능한 범위를 먼저 확인합니다. 서명 링크를 대표 주소나 장기 인용 주소로 고정하는 것도 신중해야 합니다. 시간이 지나면 만료되거나 특정 계정에서만 열릴 수 있기 때문입니다. 공개 독자가 다시 찾을 수 있는 문서 주소가 따로 있는지 확인하되 인증 우회나 권한 변경으로 해결하지 않습니다.

canonical은 URL 정리 함수가 아니라 대표 주소에 관한 신호입니다.

HTML의 rel=canonical로 대표 주소를 알릴 수 있지만 그 선언이 모든 주소를 리디렉션하거나 검색엔진의 선택을 강제하는 것은 아닙니다. Google은 리디렉션, canonical 링크, 사이트맵을 서로 다른 강도의 신호로 설명합니다. 따라서 함수가 추적값을 제거했다고 “Google의 중복 문제가 해결됐다”고 보고할 수는 없습니다.

대표 주소를 정할 때는 실제 내용이 같거나 매우 유사한지 먼저 봅니다. 언어와 페이지 번호 때문에 다른 내용을 보여 주는 주소를 무조건 첫 페이지나 한 언어로 합치면 독자가 찾는 정보를 잃을 수 있습니다. 내부 링크·사이트맵·대표 주소 선언이 서로 같은 의도를 갖는지도 확인하세요. 계정의 검색 색인 상태는 실제 도구에서 별도로 확인해야 합니다.

정리 규칙을 검토하는 표에는 삭제 가능한 이름, 삭제 근거, 남겨야 하는 기능, 실패 시 복구 방법을 넣을 수 있습니다. 예를 들어 utm_source는 유입 분석 용도라고 운영자가 확인했지만 coupon이라는 이름은 가격을 바꿀 수 있어 미확인으로 남기는 식입니다. 이 추가 이름은 제공 실행 사례가 아니라 분류표를 만드는 방법의 예시입니다. 모르는 값은 기본 삭제가 아니라 보존 또는 보류로 처리합니다.

주소 청소를 배포하기 전에 비교할 화면을 정합니다.

상품 상세, 검색 결과, 두 번째 목록 페이지, 언어별 페이지를 대표 예제로 고릅니다. 원문과 정리 주소의 HTTP 상태·최종 주소·제목·핵심 내용을 비교하고 로그인 없는 조건에서도 같은 자료인지 확인합니다. 매개변수 하나가 없는 화면이 200으로 열려도 기본 페이지로 돌아갔을 수 있으므로 상태 코드만 통과 기준으로 쓰지 않습니다.

변경 전 주소 목록을 보관하고 되돌릴 규칙을 함께 준비합니다. 링크가 여러 글에 퍼져 있다면 먼저 소수의 공개 예제로 확인한 뒤 범위를 늘립니다. 생성 규칙이 잘못됐다면 게시물마다 수동으로 수정하기보다 템플릿이나 링크 생성 코드를 고쳐야 합니다. 이번 합성 검사는 실제 사이트를 일괄 수정하는 권한이나 준비 상태를 뜻하지 않습니다.

새 canonical 주소를 정했다면 그 주소가 공개로 열리는지, 자신을 가리키는지, 다른 언어·페이지의 내용을 잘못 통합하지 않는지 확인할 필요가 있습니다. 사이트맵과 내부 링크가 오래된 다른 대표 주소를 계속 제시하면 의도가 엇갈릴 수 있습니다. 이런 운영 상태는 이번 URL 함수에서 검사하지 않습니다. 코드 출력의 rule-applied라는 상태를 검색엔진에 반영 완료라는 말로 바꾸지 마세요.

결과를 해석하는 합격 기준은 작게 유지합니다.

실행 파일은 Python에서 Node의 URL 구현을 호출합니다. 결과 rows는 세 개의 규칙 적용 결과와 한 개의 서명 주소 보류를 담습니다. 원본·출력 주소를 비교하는 assert가 통과했지만 링크 대상의 콘텐츠, 서버 리디렉션, 분석 집계, 검색엔진 대표 주소 선택은 시험하지 않았습니다. 이 경계는 배포 결과를 설명할 때도 그대로 유지해야 합니다.

매개변수 정리의 목표는 주소를 가장 짧게 만드는 것이 아니라 필요한 선택 조건을 보존하며 불필요하다고 확인한 부분만 줄이는 것입니다. 역할이 불명확한 값은 남기거나 보류합니다. 설명 가능한 삭제 목록과 비교 화면이 있으면 다음 사람이 규칙을 변경할 때도 어떤 기능을 다시 확인해야 하는지 알 수 있습니다.

검증용 사본에서 주소를 비교할 때는 결제나 계정 동작을 일으키는 경로를 제외합니다. 쿼리를 지운 요청이 항상 읽기 전용이라는 보장은 없습니다. 링크의 기능을 분류하고 필요한 공개 문서만 확인하는 절차가 먼저입니다. 또한 분석 매개변수를 제거하면 운영자의 유입 집계가 달라질 수 있으므로 기술적 동등성과 운영 정책의 승인을 별도로 남겨야 합니다.

이 글의 입력·실행 자료

합성 자료의 실제 검증일은 2026-10-15입니다. 글의 예약 발행일과 구분합니다. 코드의 fixtureCreatedOn은 예제 작성일이고 executedAt은 재실행할 때의 실제 UTC 시각입니다. Python 3 표준 라이브러리와 설치된 Node.js를 사용합니다. 실제 고객정보나 새 Gemini 응답을 입력하지 않았습니다.

python3 canonical-query-parameter-rules-lab.py

확인한 공식 원문

2026-10-15에 열어 확인했습니다. 제공 예제의 규칙과 공식 문서의 일반 기능 설명을 구분해 읽습니다.

함께 대조할 기존 기록