SEO en Django, configuraciones básicas | Programador Web Valencia

SEO en Django, configuraciones básicas

4 minutos

Django

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:

  1. Se obtienen todos los posts publicados. En mi caso los filtro por el campo is_published, pero puedes hacerlo como mejor te convenga.
  2. Se guardan las URL absolutas de los posts.
  3. A partir de la plantilla sitemap.txt se renderiza el texto plano.
  4. Se eliminan las líneas vacías.
  5. 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)

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me ayudas?

Comprame un café
Pulsa sobre la imagen

No te sientas obligado a realizar una donación, pero cada aportación mantiene el sitio en activo logrando que continúe existiendo y sea accesible para otras personas. Además me motiva a crear nuevo contenido.

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario

Tal vez también te interese...