Django 서버에서 IndexNow 등록 방법
Django 서버에서 IndexNow 등록 방법

Django 서버에서 IndexNow 등록 방법

블로그나 웹사이트를 운영하다 보면 새 게시글을 발행한 후 검색엔진에 반영되기까지 기다려야 하는 경우가 많습니다.

특히 검색 유입이 중요한 블로그, 유틸리티 사이트, 쇼핑몰 등의 경우 새로운 페이지를 빠르게 검색엔진에 알리는 것이 중요합니다.

이때 사용할 수 있는 기능이 바로 IndexNow입니다.


IndexNow란?

IndexNow는 웹사이트가 새 페이지 생성, 수정, 삭제 등의 변경 사항을 검색엔진에 즉시 알릴 수 있도록 만든 프로토콜입니다.

기존 방식은 검색엔진이 주기적으로 사이트를 크롤링해야 변경 사항을 발견할 수 있었습니다. 하지만 IndexNow를 사용하면 서버에서 직접 검색엔진에 변경된 URL을 알려줄 수 있습니다.

지원 검색엔진 예시

  • Bing
  • Naver
  • Yandex
  • Seznam
  • Yep

Google은 현재 IndexNow를 공식 지원하지 않습니다.


IndexNow 적용 전 준비

먼저 IndexNow 키를 발급받아야 합니다.

Bing Webmaster Tools 또는 IndexNow 공식 사이트에서 키를 생성할 수 있습니다.

indexnow_generate_api_key발급
indexnow_generate_api_key발급

예시

abc123456789

키를 발급받았다면 오른쪽 txt 파일을 다운로드 받거나 해당 키 이름으로 txt 파일을 생성합니다.

abc123456789.txt

파일 내용은 키 문자열만 들어있으면 됩니다.

abc123456789

txt 파일은 어디에 두어야 할까?

많은 분들이 가장 헷갈리는 부분입니다.

Django 프로젝트의 static 폴더에 파일을 넣었다고 바로 동작하는 것은 아닙니다.

중요한 것은 아래 주소가 접근 가능해야 한다는 점입니다.

https://example.com/abc123456789.txt

예를 들어 다음과 같은 구조라면

blog/static/
├── robots.txt
├── sitemap.xml
└── abc123456789.txt

collectstatic 이후 웹 서버가 해당 파일을 서비스해야 합니다.

최종적으로 브라우저에서

https://example.com/abc123456789.txt

접속 시

abc123456789

가 출력되어야 합니다.


Django 설정 추가

.env

INDEXNOW_KEY=발급받은키
INDEXNOW_HOST=example.com
INDEXNOW_KEY_LOCATION=https://example.com/발급받은키.txt

settings.py

INDEXNOW_KEY = os.getenv("INDEXNOW_KEY", "")
INDEXNOW_HOST = os.getenv("INDEXNOW_HOST", "")
INDEXNOW_KEY_LOCATION = os.getenv("INDEXNOW_KEY_LOCATION", "")

IndexNow 제출 함수 만들기

IndexNow 제추을 위한 python 파일을 생성합니다. indexnow.py 

python
import logging
import requests
from django.conf import settings

# 로그 기록용
logger = logging.getLogger(__name__)

# IndexNow 공식 API 엔드포인트
# Bing, Naver, Yandex 등 지원 검색엔진에 공유됩니다.
INDEXNOW_ENDPOINT = "https://api.indexnow.org/indexnow"


def submit_indexnow_urls(urls):
"""
IndexNow URL 제출 함수

사용 전 확인사항

1. settings.py 설정

INDEXNOW_KEY
INDEXNOW_HOST
INDEXNOW_KEY_LOCATION

2. 루트 경로에 키 파일 존재 여부

https://example.com/키값.txt

3. 제출 URL은 https:// 형식이어야 함
"""

# settings.py 에서 설정값 가져오기
key = getattr(settings, "INDEXNOW_KEY", "")
host = getattr(settings, "INDEXNOW_HOST", "")
key_location = getattr(settings, "INDEXNOW_KEY_LOCATION", "")

# 필수 설정값 누락 시 종료
if not key or not host or not urls:
logger.warning("IndexNow skipped")
return False

# https URL만 허용
url_list = [
url.strip()
for url in urls
if isinstance(url, str)
and url.strip().startswith("https://")
]

if not url_list:
logger.warning("No valid https URLs found.")
return False

# IndexNow 요청 데이터
payload = {
"host": host,
"key": key,
"keyLocation": key_location,
"urlList": url_list,
}

try:
# IndexNow API 호출
response = requests.post(
INDEXNOW_ENDPOINT,
json=payload,
timeout=8,
headers={
"Content-Type": "application/json; charset=utf-8"
},
)

# 200 또는 202 응답이면 성공
if response.status_code in (200, 202):
logger.info(
"IndexNow submitted: %s",
url_list,
)
return True

logger.warning(
"IndexNow failed. status=%s body=%s",
response.status_code,
response.text[:300],
)

return False

except requests.RequestException as exc:
logger.exception(
"IndexNow request error: %s",
exc,
)
return False



Bing, Yandex, Naver 주소를 따로 넣어야 할까?

많은 분들이 궁금해하는 부분입니다.

예전에는 Bing URL과 Yandex URL을 각각 호출하는 예제가 많았습니다.

하지만 현재는 아래 주소 하나만 사용하면 됩니다.

https://api.indexnow.org/indexnow

IndexNow 서버가 참여 검색엔진에 URL을 전달하기 때문에 Bing, Naver, Yandex 주소를 각각 호출할 필요가 없습니다.


수동 테스트 방법

management command를 만들어 테스트할 수도 있습니다. 

Django 커스텀 명령어는 다음과 같은 표준 구조를 사용합니다.


blog/
└── management/
          ├── __init__.py
          └── commands/
                    ├── __init__.py
                    └── submit_indexnow.py

management 폴더는 Django가 관리 명령어를 인식하기 위한 표준 위치이며,
commands 폴더 내부의 파일명이 곧 manage.py 명령어 이름이 됩니다.

수통 테스트 의 submit_indexnow.py 파일을 하나 생성합니다.python

from django.core.management.base import BaseCommand

# 본인 프로젝트에서 IndexNow 제출 함수가 있는 위치에 맞게 수정하세요.
# 예:
# from blog.indexnow import submit_indexnow_urls
# from blog.services.indexnow import submit_indexnow_urls
from blog.indexnow import submit_indexnow_urls


class Command(BaseCommand):
"""
Django 커스텀 management command

이 파일을 만들면 아래 명령어로 IndexNow 수동 제출이 가능합니다.

python manage.py submit_indexnow https://example.com/post/test/

여러 URL을 한 번에 제출할 수도 있습니다.

python manage.py submit_indexnow https://example.com/a/ https://example.com/b/
"""

help = "Submit one or more URLs to IndexNow"

def add_arguments(self, parser):
"""
명령어 인자 설정

urls:
제출할 URL 목록입니다.
nargs='+' 이므로 URL을 1개 이상 입력해야 합니다.
"""

parser.add_argument(
"urls",
nargs="+",
type=str,
help="IndexNow에 제출할 URL을 1개 이상 입력하세요.",
)

def handle(self, *args, **options):
"""
실제 명령어 실행 로직
"""

# 터미널에서 입력한 URL 목록 가져오기
urls = options["urls"]

# IndexNow 제출 함수 실행
ok = submit_indexnow_urls(urls)

# 결과 메시지 출력
if ok:
self.stdout.write(
self.style.SUCCESS("IndexNow submitted.")
)
else:
self.stdout.write(
self.style.WARNING("IndexNow skipped or failed.")
)


위 코드는 Django에서 직접 만든 management command입니다. 

파일명이 submit_indexnow.py이면 다음 명령어로 실행할 수 있습니다.

python manage.py submit_indexnow https://example.com/


만약 indexnow.py 파일 위치가 다르다면 import 경로만 본인 프로젝트에 맞게 수정하면 됩니다. 예를 들어 IndexNow 함수가 blog/services/indexnow.py에 있다면 다음처럼 바꿉니다.

from blog.services.indexnow import submit_indexnow_urls

정상 동작 시

IndexNow submitted.

와 같은 메시지가 출력됩니다.


게시글 발행 시 자동 제출

게시글이 발행될 때 자동으로 IndexNow를 호출하면 관리가 매우 편리합니다.  python 파일을 하나 생성해줍니다. indexnow_signal.py 

예시

python
from django.db.models.signals import post_save
from django.dispatch import receiver

from blog.models import Post
from blog.indexnow import submit_indexnow_urls


# 본인 사이트 도메인으로 변경하세요.
# 예: https://example.com
# 예: https://win-j.com
SITE_BASE_URL = "https://example.com"


@receiver(post_save, sender=Post)
def submit_post_to_indexnow(sender, instance, **kwargs):
"""
게시글이 저장될 때 IndexNow에 URL을 제출합니다.

확인해야 할 부분
1. Post 모델에 status 필드가 있는지
2. 발행 상태 값이 "published"가 맞는지
3. Post 모델에 visibility 필드가 있는지
4. 공개 상태 값이 "public"이 맞는지
5. get_absolute_url()이 실제 게시글 URL을 반환하는지
"""

# 임시글은 제출하지 않습니다.
# 본인 프로젝트의 발행 상태 값에 맞게 수정하세요.
if instance.status != "published":
return

# 비공개 글이나 보호 글은 제출하지 않습니다.
# visibility 필드가 없는 프로젝트라면 이 부분은 삭제해도 됩니다.
if instance.visibility != "public":
return

# get_absolute_url()은 "/post-slug/" 같은 상대 경로를 반환해야 합니다.
# 예: /django-indexnow/
post_url = f"{SITE_BASE_URL}{instance.get_absolute_url()}"

submit_indexnow_urls([post_url]


위 코드에서 반드시 확인해야 할 부분은 status, visibility, get_absolute_url()입니다.

모든 Django 프로젝트가 같은 Post 모델을 사용하는 것은 아니기 때문에,
본인 프로젝트의 게시글 모델에 맞게 조건문을 수정해야 합니다.

예를 들어 발행 상태 값이 "publish"라면 다음처럼 바꿔야 합니다.

python
if instance.status != "publish":
return

visibility 필드가 없는 프로젝트라면 아래 코드는 삭제해도 됩니다.

python
if instance.visibility != "public":
return

마지막으로 get_absolute_url()이 없다면 직접 URL을 만들어야 합니다.

python
post_url = f"https://example.com/blog/{instance.slug}/"


자주 발생하는 오류

1. txt 파일이 404로 나오는 경우

원인

  • 파일 위치가 잘못됨
  • collectstatic 미실행
  • Nginx 설정 문제

확인

https://example.com/키값.txt

접속 시 키 문자열이 보여야 합니다.


2. collectstatic 이후에도 파일이 안 보이는 경우

원인

static 폴더에는 있지만 실제 서비스 경로로 복사되지 않은 경우입니다.

실행

python manage.py collectstatic --noinput

3. API 호출은 성공했는데 검색에 안 나오는 경우

IndexNow는 색인 요청 기능입니다. 색인을 보장하는 기능은 아닙니다.

다음 항목도 함께 관리해야 합니다.

  • sitemap.xml
  • robots.txt
  • Search Console
  • Bing Webmaster Tools
  • Naver Search Advisor
  • 내부 링크 구조
  • 콘텐츠 품질

    마무리

    IndexNow는 구현이 어렵지 않지만 적용 후 효과는 상당히 큰 기능입니다.

    특히 게시글이 자주 올라오는 블로그나 다양한 유틸리티 페이지를 운영하는 사이트라면 자동 제출 기능을 추가해두는 것이 좋습니다. 한 번 설정해두면 게시글 발행과 동시에 Bing, Naver, Yandex 등 지원 검색엔진에 변경 사항을 빠르게 알릴 수 있어 검색 노출 속도를 높이는 데 도움이 됩니다.