Большинство туториалов в сети по запросу "how to create custom 404 page in Django" некорректны, т.к. мало где указывают что помимо переопределения шаблона для 404 ошибки надо еще возвращать корректный статус-код. Не 200, а 400-й.
Как проверить код ответа в Http заголовках браузера читайте в статье How to view HTTP headers in Google Chrome?. А я приведу пример ниже корректной кастомизации страниц с ошибками:
В корневом urls.py
from django.conf.urls import (
# handler400,
# handler403,
handler404,
# handler500,
)
...
handler404 = 'apps.core.views.error_404'
# handler400 = 'apps.core.views.my_custom_bad_request_view'
# handler403 = 'apps.core.views.my_custom_permission_denied_view'
# handler500 = 'apps.core.views.my_custom_error_view'
В app где переопределяете вьюху views.py
...
def error_404(request, exception):
context = {}
context['page_title'] = '404'
response = render(request, 'errors/404.html', context=context)
response.status_code = 404
return response
Далее оформите 404.html как вам нравится и Готово!
Как видите из примера подобным образом можно кастомизировать оформление страниц с другими ошибками.
Не забудьте проверить корректность Status Code в Dev tools > Network > Headers вашего браузера.