게시할 때마다 완성된 공개 페이지 세트에서 사이트맵을 재생성한 다음, 둘을 함께 업로드하세요. 이 Mac 워크플로는 간단한 Python 스크립트를 사용해 추가된 페이지를 포함하고 삭제된 페이지를 자동으로 제외하며, 공개 결과도 확인합니다.

여기서는 Mac에서 내보낸 소규모 정적 웹사이트의 사이트맵을 유지 관리합니다. 예시에서는 색상에 관한 가상 페이지를 사용합니다. 기존 게시 과정에서 내보낸 폴더를 업로드하고 공개 사이트에서 더 이상 필요 없는 파일을 삭제할 수 있다고 가정합니다.

사이트맵 재생성을 어디에 둘지 정하기

먼저 게시 시스템이 이미 사이트맵을 생성하는지 확인하세요. Google에 따르면 많은 콘텐츠 관리 시스템이 사이트맵을 자동으로 제공합니다. 해당 시스템을 사용한다면 생성기를 하나 더 추가하기 전에 테스트 페이지를 추가하고 삭제한 뒤 사이트맵이 어떻게 바뀌는지 확인하세요. 사이트맵 알아보기.

정적 내보내기를 사용하는 경우 다음 순서를 따르세요. 페이지 내보내기를 완료하고, 내보낸 결과에서 사이트맵을 생성한 다음, 결과 폴더를 게시합니다. 이는 제안된 구현 방식으로, 사이트맵의 페이지 목록이 게시 시스템이 받는 페이지 세트와 일치하도록 합니다.

게시 과정에서 생성하면 페이지 추가와 삭제에 동일한 규칙을 적용할 수 있습니다. 완성된 내보내기에 있는 페이지는 포함되고, 없는 페이지는 제외됩니다. 별도로 기억해 두고 업데이트해야 할 목록도 없습니다.

기존 설정에 맞는 통합 방식을 선택하세요.

기존 게시 설정 재생성 위치
CMS가 이미 사이트맵을 관리함 기존 생성기를 유지하고 페이지 변경 시 동작을 테스트합니다
정적 내보내기 도구가 내보내기 후 명령 실행을 지원함 업로드 전에 생성기를 실행합니다
기존 명령이 완성된 폴더를 게시함 생성기의 선택적 게시 인수를 통해 실행합니다
데스크톱 인터페이스로 업로드함 먼저 생성한 다음 전체 폴더를 업로드합니다. 공개 사이트를 자동으로 업데이트하려면 지원되는 게시 통합이 필요합니다

로컬 스크립트만으로 원격 웹사이트를 변경할 수는 없습니다. 아래 워크플로는 사이트맵 생성을 자동화하며 기존 게시 명령을 실행할 수도 있습니다. 실제 업로드는 기존 게시 시스템이 담당합니다.

사이트맵에 포함할 페이지를 정확히 정하기

일반 페이지 URL의 경우 Google은 한 줄에 URL 전체를 하나씩 담은 텍스트 사이트맵을 허용합니다. 이 작업에는 XML이 필요하지 않습니다. 같은 콘텐츠를 가리키는 여러 URL 버전 대신 각 페이지의 대표 공개 URL을 포함하세요. 사이트맵 작성 및 제출.

텍스트 형식은 UTF-8 인코딩이어야 하며, 제목이나 주석 없이 URL만 포함합니다. 파일 하나에는 URL을 최대 50,000개, 바이트를 최대 52,428,800개까지 넣을 수 있습니다. 내보낸 페이지를 해당 위치 아래에서 모두 포함하도록 이 예시의 사이트맵을 사이트 루트에 둡니다. 사이트맵 프로토콜: 텍스트 파일과 위치.

이 생성기는 의도적으로 단순한 내보내기 규칙을 사용합니다.

내보낸 파일 공개 URL 예시
public/index.html https://example.com/
public/colors/blue.html https://example.com/colors/blue.html
public/tiles/index.html https://example.com/tiles/

호스트가 정확히 해당 URL을 제공하는 경우에만 이 규칙을 사용하세요. 게시 시스템이 blue.html을 /colors/blue/로 바꾸거나 사이트를 /collection/ 같은 경로 아래에서 제공한다면, 게시 시스템의 경로 목록을 사용하거나 게시 전에 매핑을 조정하세요. 파일명 검색만으로는 사용자 지정 경로를 파악할 수 없습니다.

내보내기 폴더에 포함할 페이지 파일만 유지하세요. 이 예시에서는 점으로 시작하는 경로와 404.html이라는 이름의 파일을 건너뜁니다. 페이지 메타데이터, 표준 URL 선언 또는 리디렉션 규칙은 검사하지 않습니다. 이런 조건도 고려해야 하는 사이트라면 게시 시스템의 적격 페이지 목록에서 생성해야 합니다.

일반적인 탐색 링크도 계속 관리하세요. WHATWG는 a 요소의 href 속성으로 만드는 하이퍼링크를 정의하며, Google은 올바르게 링크된 페이지는 보통 탐색을 통해 발견할 수 있다고 설명합니다. 사이트맵을 업데이트할 때는 추가 또는 삭제된 페이지를 가리키는 링크도 함께 업데이트하세요. WHATWG HTML 표준: 링크, 사이트맵 알아보기.

Mac에서 생성기 만들기

Python 3.8 이상과 일반 텍스트 편집기가 필요합니다. 터미널에서 python3 --version을 실행해 사용할 수 있는 인터프리터를 확인하세요. Python이 필요하면 Python Software Foundation의 macOS 설치 안내를 따르세요. 해당 문서에는 터미널에서 python3로 스크립트를 실행하는 방법도 설명되어 있습니다. macOS에서 Python 사용하기.

데스크톱에 sitemap-demo이라는 폴더를 만드세요. 아래의 완전한 프로그램을 그 안에 regenerate_sitemap.py이라는 이름으로 저장합니다. 이 프로그램은 Python 표준 라이브러리를 사용하므로 추가 패키지가 필요하지 않습니다.

import argparse
from pathlib import Path, PurePosixPath
import subprocess
import tempfile
from urllib.parse import quote, urlsplit


def make_sitemap(paths, origin):
    parsed = urlsplit(origin)
    if (parsed.scheme not in ("http", "https") or not parsed.hostname
            or parsed.netloc != parsed.hostname
            or parsed.path not in ("", "/")
            or parsed.query or parsed.fragment):
        raise ValueError("Use a site origin, such as https://example.com")
    base = origin.rstrip("/")
    urls = set()
    for relative in paths:
        path = PurePosixPath(relative)
        if path.name == "index.html":
            parent = path.parent.as_posix()
            route = "" if parent == "." else parent + "/"
        else:
            route = path.as_posix()
        urls.add(base + "/" + quote(route, safe="/"))
    if not urls:
        raise ValueError("No eligible pages; sitemap was not replaced")
    if len(urls) > 50000:
        raise ValueError("Split the sitemap: more than 50,000 URLs")
    data = ("\n".join(sorted(urls)) + "\n").encode("utf-8")
    if len(data) > 50 * 1024 * 1024:
        raise ValueError("Split the sitemap: larger than 50 MB")
    return data


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("folder")
    parser.add_argument("origin")
    parser.add_argument("publish", nargs=argparse.REMAINDER)
    args = parser.parse_args()
    root = Path(args.folder).resolve()
    if not root.is_dir():
        parser.error("The export folder must already exist")
    paths = []
    for file in root.rglob("*.html"):
        relative = file.relative_to(root)
        if (file.is_file() and file.name != "404.html"
                and not any(p.startswith(".") for p in relative.parts)):
            paths.append(relative.as_posix())
    data = make_sitemap(paths, args.origin)
    target = root / "sitemap.txt"
    temporary = None
    try:
        with tempfile.NamedTemporaryFile(dir=root, delete=False) as handle:
            temporary = Path(handle.name)
            handle.write(data)
        temporary.replace(target)
    finally:
        if temporary is not None:
            temporary.unlink(missing_ok=True)
    print(f"Wrote {len(data.splitlines())} URLs to {target}", flush=True)
    if args.publish:
        subprocess.run(args.publish, check=True)


if __name__ == "__main__":
    main()

실행할 때마다 URL 목록 전체를 다시 구성합니다. 결과를 정렬하고 중복을 제거하며, 경로의 공백 같은 문자를 인코딩하고, 임시 파일에 쓴 다음 sitemap.txt을 교체합니다. 생성 중 오류가 발생하면 선택적 게시 명령을 실행하기 전에 프로그램이 중단됩니다.

내보내기가 비어 있으면 의도적으로 오류를 발생시키고 기존 사이트맵은 그대로 둡니다. 실수로 잘못된 폴더를 선택했거나 내보내기가 불완전한 경우를 잡아냅니다. 모든 페이지를 의도적으로 제거하려면 이 워크플로에 빈 내보내기를 전달하는 대신 별도로 게시 방법을 결정해야 합니다.

게시 시스템을 연결하기 전에 페이지 추가와 삭제 테스트하기

터미널에서 데모 폴더로 이동합니다.

cd "$HOME/Desktop/sitemap-demo"

아래의 완전한 설정 명령으로 가상 페이지 두 개를 만듭니다. 새 데모 폴더에서 실행하면 이름이 지정된 샘플 파일 두 개가 작성됩니다.

python3 - <<'PY'
from pathlib import Path

root = Path("public")
(root / "colors").mkdir(parents=True, exist_ok=True)
pages = {
    "index.html": "<!doctype html><title>Color collection</title><p>Color collection.</p>",
    "colors/blue.html": "<!doctype html><title>Blue tile</title><p>A blue tile.</p>",
}
for name, content in pages.items():
    (root / name).write_text(content, encoding="utf-8")
PY

로컬 사이트맵을 생성하고 확인합니다. 예시 출처는 설명을 위한 것으로, 아래 명령은 그곳에 아무것도 게시하지 않습니다.

python3 regenerate_sitemap.py public https://example.com
cat public/sitemap.txt

예상 파일에는 다음 두 줄만 들어 있습니다.

https://example.com/
https://example.com/colors/blue.html

이제 가상의 파란색 페이지를 초록색 페이지로 바꿉니다.

python3 - <<'PY'
from pathlib import Path

root = Path("public")
(root / "colors/blue.html").unlink()
(root / "colors/green.html").write_text(
    "<!doctype html><title>Green tile</title><p>A green tile.</p>",
    encoding="utf-8",
)
PY
python3 regenerate_sitemap.py public https://example.com
cat public/sitemap.txt

예상 결과에는 홈페이지 URL 다음에 https://example.com/colors/green.html이 나옵니다. 내보낸 파일이 없어졌으므로 파란색 URL은 사라집니다. 이렇게 하면 검색 엔진이 변경 사항을 알아차리기를 기다리지 않고 추가와 삭제가 모두 반영되는지 확인할 수 있습니다.

모든 게시 과정에 재생성 포함하기

실제 사이트에 적용하려면 데모 폴더와 출처를 실제로 완성된 내보내기 폴더와 공개 출처로 바꾸세요. 내보내기가 끝난 뒤에만 생성기를 실행하고, 게시가 완료될 때까지 해당 폴더를 변경하지 마세요. 게시 작업은 순차적으로 실행합니다.

프로젝트 폴더에 deploy-site.sh이라는 기존 게시 스크립트가 있다면 다음 통합 명령이 사이트맵을 생성한 뒤 해당 스크립트를 실행합니다. 이 예시에서는 기존 스크립트가 필요합니다. 생성기가 해당 스크립트를 만들거나 호스팅 설정을 제공하지는 않습니다.

python3 regenerate_sitemap.py public https://example.com sh ./deploy-site.sh

게시 스크립트는 public 폴더 전체를 업로드해야 하며, 여기에는 sitemap.txt도 포함됩니다. 또한 내보내기에서 사라진 공개 파일을 삭제해야 합니다. 사이트맵을 생성한 뒤 페이지를 다시 빌드해서는 안 됩니다. 사이트도 빌드하는 게시 시스템이라면 빌드 단계와 업로드 단계 사이에 사이트맵 생성을 넣으세요.

생성에 실패하면 게시 명령은 건너뜁니다. 게시에 실패하면 Python이 오류를 보고하지만 원격 사이트가 이미 일부 변경되었을 수 있습니다. 재시도하기 전에 공개 결과를 확인하세요. 호스트가 전체 릴리스를 한 번에 게시하는 기능을 지원한다면 해당 문서에 설명된 방법을 사용하세요.

데스크톱 업로더를 사용하는 경우 내보내기 폴더를 업로드 대상으로 선택하기 전에 사이트맵을 생성하세요. 업로더가 기존 사이트맵을 교체하고 삭제된 페이지를 처리하는지 확인합니다. 이 방식에도 업로드 작업은 필요합니다. 로컬에서 자동 생성하는 것만으로는 공개 사이트에 사람의 개입 없이 게시할 수 없습니다.

공개 사이트맵과 삭제된 페이지를 따로 확인하기

게시가 성공한 뒤 브라우저에서 실제 공개 sitemap.txt URL을 여세요. 추가한 페이지가 표시되고 삭제한 페이지가 표시되지 않는지 확인합니다. 그런 다음 영향을 받은 각 페이지에 직접 접속합니다. 이렇게 하면 로컬 내보내기가 올바른지, 공개 릴리스가 올바른지 구분할 수 있습니다.

사이트맵에서 항목을 삭제해도 해당 페이지의 이전 주소가 어떤 HTTP 응답을 반환하는지는 정해지지 않습니다. 대체 페이지가 없는 삭제된 페이지라면 호스트가 적절한 페이지 없음 응답을 반환하는지 확인하세요. RFC 9110은 현재 표현이 없는 리소스에 404을, 리소스를 사용할 수 없으며 그 상태가 영구적일 가능성이 높은 경우에 410를 정의합니다. RFC 9110: HTTP 의미 체계.

페이지가 페이지 없음 메시지를 표시하면서 200을 반환하면 소프트 404으로 처리될 수 있습니다. Google은 이전에 색인된 URL이 해당되는 4xx 응답을 반환하면 시간이 지나면서 색인에서 삭제된다고도 설명합니다. 따라서 사이트맵에서 URL을 삭제하는 것은 페이지를 제거하는 과정의 한 부분일 뿐입니다. HTTP 상태 코드가 Google 크롤러에 미치는 영향.

문제가 있어 보일 때는 다음 순서로 확인하세요.

관찰한 현상 다음 확인 사항
로컬 사이트맵에 삭제한 페이지가 계속 표시됨 내보낸 파일이 남아 있거나 잘못된 내보내기 폴더를 사용하고 있는지 확인합니다
로컬 사이트맵은 올바르지만 공개 사이트맵은 다름 업로드한 폴더, 대상 위치 및 캐시를 확인합니다
공개 사이트맵은 올바르지만 삭제한 페이지가 여전히 열림 게시 과정에서 기존 원격 파일을 삭제했는지 확인합니다
생성된 URL이 잘못된 페이지를 엶 호스트의 경로 매핑이 파일명 규칙과 일치하는지 확인합니다
새 URL은 공개되었지만 검색 결과에 나타나지 않음 검색 엔진의 발견과 색인은 별도의 과정으로 취급합니다

캐시 때문에 공개 사이트맵이 바뀌지 않은 것처럼 보일 수 있습니다. RFC 9111은 캐시된 응답이 최신 상태로 유지되고 재검증되는 방식을 정의합니다. 호스트에서 응답 헤더를 설정할 수 있다면 Cache-Control: no-cache는 저장된 응답을 재사용하기 전에 검증을 성공적으로 마치도록 합니다. 로컬 파일만 변경해서는 해당 헤더가 설정되지 않습니다. RFC 9111: HTTP 캐싱.

안정적인 사이트맵 주소 하나 유지하기

Search Console의 Sitemaps 보고서를 통해 사이트맵의 고정 URL을 제출하거나 사이트의 기존 robots.txt 파일에 해당 URL을 적어 공개 사이트맵을 찾을 수 있게 하세요. Google은 두 방법을 모두 설명하며 제출은 참고 정보라고 명시합니다. 사이트맵 작성 및 제출.

예시 사이트에서 검색 엔진에 알리는 줄은 다음과 같습니다.

Sitemap: https://example.com/sitemap.txt

게시할 때 내용을 교체하되 사이트맵 주소는 안정적으로 유지하세요. 완료 여부는 공개 사이트맵이 완성된 내보내기와 일치하고, 추가한 페이지가 표시된 URL에서 열리며, 삭제한 페이지가 의도한 응답을 반환하는지 확인하면 됩니다. 검색 엔진은 각자의 일정에 따라 변경 사항을 처리합니다. 사이트맵에 포함된다고 해서 크롤링이나 색인이 보장되지는 않습니다. 사이트맵 알아보기.

출처