
Me gustaría compartir las configuraciones mínimas que utilizo en mis proyectos Django para mejorar el SEO. Desde la creación de un sitemap, robots.txt, humans.txt, hasta la configuración de los errores 404 y 500. Los elementos básicos para cualquier página dinámica que quiera posicionarse en los buscadores.
Archivos de texto plano
Primero configuraremos las vistas que nos servirán para generar los archivos de texto plano, como el robots.txt
, humans.txt
y security.txt
. Estos archivos se encuentran en la raíz del proyecto.
Personalmente los creo dentro de la carpeta de las plantillas, dentro de una carpeta llamada txts
, los siguientes archivos:
robots.txt
, encargado de indicarle a los buscadores que páginas pueden indexar y cuales no.
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /static/
Sitemap: {{ domain_url }}/sitemap.txt
humans.txt
, archivo donde se indica el equipo de desarrollo, su ubicación y un dato curioso.
/* TEAM */
<empresa>
CEO: <nombre>
CTO/Backend: <nombre>
Site: <url>
Mastodon: @<usuario>
X: @<usuario>
From: <ciudad>, <pais>
Graphic and UI/UX designer: <nombre>
Frontend Developer and Web designer: <nombre>
/* SITE */
Last update:
Language: English, Español, Catalán
Standards: HTML5, CSS3
Components: Quasar, VueJS
IDE: WebStorm, PyCharm, VSCode, Emacs
security.txt
, lugar donde se indica la política de seguridad de la página o dirección de contacto en caso de encontrar una vulnerabilidad.
Contact: security@midominio.com
Ahora creamos las vistas que nos servirán para renderizar los archivos de texto plano.
from django.shortcuts import render
def robots(request):
return render(request, "txts/robots.txt", context={"domain_url": "https://example.com"}, content_type="text/plain")
def security(request):
return render(request, "txts/security.txt", content_type="text/plain")
def humans(request):
return render(request, "txts/humans.txt", content_type="text/plain")
Ahora indicamos las rutas en el archivo urls.py
.
from django.urls import path
urlpatterns = [
path("robots.txt", views.robots, name="robots"),
path("security.txt", views.security, name="security"),
path("humans.txt", views.humans, name="humans"),
]
Sitemap
Dentro de Django existe un framework llamado syndication feed que te permite crear renderizados RSS, Atom o XML. Sin embargo, para crear un Sitemap podemos reducir complejidad y mejorar la personalización. Si lees con atención la documentación de Google, descubrirás que puedes crear un Sitemap usando XML o ¡texto plano! Solo necesitamos devolver un texto plano con las URL de nuestro sitio web, una por línea.
Cuando creamos un Sitemap, debemos tener en cuenta que las URL deben ser absolutas. Crearemos un templatetags
para obtener las URL absolutas de cualquier página de nuestro sitio web.
Dentro de tu aplicación, crea una carpeta llamada templatetags
y dentro de ella un archivo llamado custom_tags.py
con el siguiente contenido.
from django.conf import settings
from django.urls import reverse
from django import template
from app.public.models import CaseStudyListPage, BlogListPage
register = template.Library()
@register.simple_tag
def url_absolute(page):
return settings.DOMAIN_URL + reverse(page)
Revisa en tu archivo settings.py
que exista la variable DOMAIN_URL
con la URL de tu sitio web. En caso contrario, añádela.
DOMAIN_URL = "https://misitio.com"
A continuación creamos una plantilla, la llamaremos sitemap.txt
y la guardaremos en la carpeta de las plantillas con el siguiente contenido.
{% url_absolute "home" %}
{% url_absolute "about-us" %}
{% url_absolute "contact" %}
{# Posts #}
{% if posts %}
{% for url_post in posts %}
{{ url_post }}
{% endfor %}
{% endif %}
Estamos renderizando las URL absolutas de 3 páginas estáticas de ejemplo (home
, about-us
y contact
) y las URL de los posts que tengamos en la base de datos.
Dentro del modelo de Post
creamos un método que nos devuelva la URL absoluta del post.
from django.db import models
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
content = models.TextField()
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self): # Nuevo
return reverse("post", kwargs={"slug": self.slug}) # Nuevo
Ahora crearemos en la vista toda la lógica para renderizar el sitemap.
from django.http import HttpResponse
from django.conf import settings
from django.template.loader import render_to_string
def sitemap(request):
"""Sitemap txt"""
# Get all published posts
posts_published = list(filter(lambda post: post.is_published, Post.objects.all()))
posts_all_urls = list(map(lambda post: post.absolute_url, posts_published))
# Remove empty lines
text_with_empty_lines = render_to_string(
"sitemap.txt",
{
"posts": posts_all_urls,
},
)
text_without_empty_lines = "\n".join([line.strip() for line in text_with_empty_lines.splitlines() if line.strip()])
# Render sitemap txt
return HttpResponse(text_without_empty_lines, content_type="text/plain")
Se realizan las siguientes acciones:
- Se obtienen todos los posts publicados. En mi caso los filtro por el campo
is_published
, pero puedes hacerlo como mejor te convenga. - Se guardan las URL absolutas de los posts.
- A partir de la plantilla
sitemap.txt
se renderiza el texto plano. - Se eliminan las líneas vacías.
- Se devuelve el texto plano mediante un
HttpResponse
.
Finalmente, indicamos la ruta en el archivo urls.py
.
from django.urls import path
urlpatterns = [
path("sitemap.txt", views.sitemap, name="sitemap"),
]
Si estas usando Wagtail, deberás cambiar la forma que obtienes los posts, ya que no se encuentran en la base de datos, sino en el CMS.
def sitemap(request):
"""Sitemap txt"""
# Get all published posts
post_parent_url = BlogPage.objects.live().first().get_parent().get_full_url()
posts_published = BlogPage.objects.live()
posts_all_urls = [post_parent_url] + list(
map(lambda post: post.get_full_url(), posts_published)
)
# Remove empty lines
text_with_empty_lines = render_to_string(
"seo/sitemap.txt",
{
"posts": posts_all_urls,
},
)
text_without_empty_lines = "\n".join(
[line.strip() for line in text_with_empty_lines.splitlines() if line.strip()]
)
# Render sitemap txt
return HttpResponse(text_without_empty_lines, content_type="text/plain")
Errores 404 y 500
Para personalizar los errores 404 y 500, creamos una carpeta llamada errors
dentro de la carpeta de las plantillas. Dentro de esta carpeta creamos los archivos 404.html
y 500.html
con el contenido que queramos.
def handler404(request, *args, **argv):
return render(request, "errors/404.html", status=404)
def handler500(request, *args, **argv):
return render(request, "errors/500.html", status=500)
{{ comments.length }} comentarios