Redirecciones en Django: redirect(), HttpResponseRedirect y PRG

- Andrés Cruz - EN In english

Video thumbnail

Cuando desarrollamos cualquier proyecto web, Django no es la excepción: además de tener rutas con nombre para organizar nuestras URLs, necesitamos manejar redirecciones. Las redirecciones son una de esas herramientas que usamos casi sin pensarlo, pero que tienen un papel clave tanto en la experiencia del usuario como en el SEO técnico.

Usar bien las redirecciones es lo que marca la diferencia entre una app ordenada y un caos de enlaces rotos.

Las redirecciones en Django son el mecanismo para enviar al usuario de una URL a otra, ya sea dentro de nuestra aplicación o hacia un dominio externo. En términos técnicos, una redirección no es más que una respuesta HTTP de tipo 3xx que le indica al navegador a qué URL debe ir a continuación. Un ejemplo clásico: cuando cambiamos el slug de un post sin querer perder el posicionamiento de la URL original, implementamos una redirección 301 para que Google transfiera el valor SEO hacia el nuevo enlace.

1. El problema de devolver la página directamente tras un POST

Si una vista POST —por ejemplo, la de guardar o actualizar un registro— responde devolviendo directamente el template con render(), ocurre lo siguiente:

  • El navegador se queda en un estado POST.
  • Si el usuario recarga la página o usa el botón "Atrás", el navegador intentará reenviar la misma petición POST, lo que puede duplicar el envío, la actualización o incluso la eliminación del registro.

Este comportamiento no solo es un problema de UX, también puede corromper datos si no se maneja con cuidado.

2. ✅ La solución: el patrón PRG (Post/Redirect/Get)

Para evitar que la petición POST se reenvíe involuntariamente, usamos el patrón PRG (Post/Redirect/Get), un estándar muy extendido en el desarrollo web:

  • Post: La vista recibe la petición POST y procesa la lógica de negocio.
  • Redirect: Después de guardar o eliminar el registro, la vista devuelve una respuesta de redirección en lugar de renderizar un template.
  • Get: El navegador realiza una nueva petición GET a la URL indicada por la redirección, generalmente el listado o la página de detalle del registro.

¿Qué es una redirección HTTP y por qué importa en Django?

Una redirección HTTP es simplemente una instrucción que le dice al navegador: “no te quedes aquí, ve a otra URL”.
En Django, este comportamiento se implementa con funciones y clases específicas que devuelven una respuesta HTTP de tipo 3xx.

Qué significa redirigir una vista en Django

Cuando una vista procesa un formulario o una acción concreta, muchas veces no tiene un template asociado, porque su única responsabilidad es ejecutar una operación y enviar al usuario a otro lugar. En mis proyectos, por ejemplo, cuando un usuario crea un registro, lo envío directamente a la página de detalle o al listado general, según la lógica de negocio que corresponda.

Códigos de estado más usados: 301, 302 y 307

  • 301 (Permanente): indica que la URL ha cambiado definitivamente. Es el código ideal para SEO y para cuando modificas el slug de una página.
  • 302 (Temporal): se usa cuando la redirección es momentánea, por ejemplo, al redirigir tras enviar un formulario o mientras pruebas una vista.
  • 307 (Temporal estricto): es una versión más estricta del 302; garantiza que el método original (POST o GET) se preserve en la redirección.

Cuándo usar 301 y cuándo 302: guía rápida para SEO

Cuando cambié el slug de un post en mi blog, usé una redirección 301 para no perder tráfico ni backlinks. Los motores de búsqueda interpretan el 301 como una transferencia definitiva del valor SEO de la URL original hacia la nueva.

En cambio, para redirecciones internas tras enviar formularios, siempre uso 302, ya que son transitorias y no deben transferir señales SEO.

Métodos para hacer redirecciones en Django

Las redirecciones son el flujo habitual cuando una vista no tiene un template asociado. Por ejemplo, cuando procesamos la creación de un registro, el resultado esperado es:

  1. Si los datos son correctos: hacemos una redirección al listado, o al formulario de edición del registro recién creado.
  2. Si la validación falla: redirigimos de vuelta al formulario de creación mostrando los errores correspondientes.

Para los siguientes ejemplos, partimos de estas rutas definidas en urls.py:

app_name = "gestion"
urlpatterns = [
   path('', views.index, name="index"),
   path('detail/<int:pk>', views.show, name="show"),
   path('create', views.create, name="create"),
   path('update/<int:pk>', views.update, name="update"),
   path('delete/<int:pk>', views.delete, name="delete"),
]

Django ofrece varias formas de redirigir, dependiendo del contexto y del nivel de control que necesitemos.

Usar redirect() en vistas (el método más directo)

La función redirect(), disponible en django.shortcuts, es el método más cómodo y el que uso en la mayoría de mis proyectos. Acepta el nombre de una ruta con namespace, una URL relativa o una URL absoluta:

from django.shortcuts import redirect

return redirect('gestion:index')

Un ejemplo más completo dentro de una vista de creación:

from django.shortcuts import redirect

def create(request):
    form = ProductForm(request.POST or None)
    if form.is_valid():
        product = Product()
        product.title = form.cleaned_data['title']
        # ... resto de los campos ...
        product.save()
        return redirect('gestion:update', pk=product.id)
    return redirect('gestion:create')

El patrón es sencillo:

  • Si todo sale bien, redirijo al formulario de edición del registro recién guardado.
  • Si algo falla en la validación, vuelvo a la vista de creación para que el usuario corrija los errores.

HttpResponseRedirect y HttpResponsePermanentRedirect

Cuando necesitas un control más explícito sobre el código de estado HTTP, puedes usar directamente las clases HttpResponseRedirect (que devuelve un 302) y HttpResponsePermanentRedirect (que devuelve un 301):

from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect

# Redirección temporal (302)
return HttpResponseRedirect('/productos/')

# Redirección permanente (301) — útil para cambios de slug y SEO
return HttpResponsePermanentRedirect('/nuevo-producto/')

Son útiles cuando quieres dejar explícito en el código qué tipo de redirección estás aplicando, sin depender del comportamiento implícito de redirect().

Redirecciones con reverse() y parámetros dinámicos

Django permite construir la URL a partir del nombre de la ruta y sus parámetros usando reverse(). Esto es especialmente útil cuando trabajas con rutas que tienen parámetros dinámicos como pk o slug:

from django.urls import reverse

return redirect(reverse('gestion:show', kwargs={'pk': 15}))

Es una práctica más segura que hardcodear URLs, sobre todo si en algún momento cambias la estructura de tus rutas en urls.py: con reverse(), solo tendrías que actualizar el nombre de la ruta en un único lugar.

Redirecciones a URLs externas o hacia otros dominios

Si necesitas redirigir fuera de tu aplicación, simplemente pasa la URL completa como string:

return redirect('https://www.google.com')

Ten cuidado con las redirecciones abiertas (open redirects): si la URL de destino proviene de la petición del usuario (por ejemplo, un parámetro ?next=), valídala siempre antes de redirigir para evitar vulnerabilidades de seguridad.

redirect() con parámetros posicionales

También puedes pasar parámetros directamente a redirect() sin necesidad de usar reverse():

return redirect('gestion:update', pk=15)

Django construirá la URL automáticamente a partir del nombre de la ruta y el valor del parámetro indicado.

Casos prácticos que uso en mis proyectos

  • Redirigir tras crear o actualizar un registro
    • En mi flujo habitual, después de guardar un producto, redirijo al detalle o al listado.
      Esto mejora la UX y evita que el usuario recargue el formulario accidentalmente, duplicando la operación.
  • Manejo de formularios: validar, guardar y redirigir correctamente
    • Si los datos no son válidos, siempre redirijo al formulario original mostrando los errores.
      Es un pequeño gesto que evita confusión al usuario y mantiene la app fluida y predecible.
  • Cambiar el slug de un post y redirigir con 301 para no perder tráfico
    • Una vez, al actualizar los títulos de varios artículos, los slugs antiguos quedaron obsoletos. Implementé una redirección 301 en urls.py hacia las nuevas rutas. El resultado fue claro: mantuve el tráfico orgánico y no perdí posicionamiento.
  • Cómo evitar bucles infinitos y errores comunes
    • Un error típico es redirigir a la misma vista que genera la redirección, creando un bucle infinito que el navegador termina cortando con un error ERR_TOO_MANY_REDIRECTS.
    • Para evitarlo, verifica siempre las condiciones antes de devolver la respuesta de redirección:
    • if request.path != reverse('home'):
          return redirect('home')

Probar y verificar tus redirecciones

Cómo testear redirecciones con el Client() de Django

Puedes automatizar pruebas para asegurarte de que tus vistas redirigen correctamente, sin necesidad de abrir el navegador:

from django.test import Client

client = Client()
response = client.post('/crear/', {'title': 'Nuevo'})
assert response.status_code == 302

Usar assertRedirects() en tests automatizados

Más elegante y expresivo aún: el método assertRedirects() verifica tanto el código de estado como la URL de destino en una sola línea:

self.assertRedirects(response, '/listado/')

Comprobar tus redirecciones con curl o herramientas SEO

Desde la terminal, puedes inspeccionar los encabezados HTTP de cualquier URL con:

$ curl -I https://tusitio.com/old-url/

Así verificas rápidamente si el código devuelto es 301 o 302 y hacia dónde apunta la cabecera Location.
También puedes revisar tus redirecciones en Google Search Console o herramientas como Screaming Frog, que permiten rastrear toda la cadena de redirecciones de tu sitio.

Errores frecuentes y cómo solucionarlos

NoReverseMatch y namespaces mal definidos

Este error ocurre cuando el nombre de la URL no coincide con el registrado en urls.py. Revisa que tengas bien definidos tanto el app_name como los path():

app_name = 'gestion'
path('detalle/<int:pk>', views.show, name='show')

Llamar a redirect('gestion:show', pk=1) con el namespace y el nombre correctos solucionará el error.

Redirecciones en login/logout que no funcionan

Si usas vistas genéricas como LoginView, define el parámetro next o success_url correctamente para controlar el destino tras el login:

from django.contrib.auth.views import LoginView
from django.urls import reverse_lazy

class CustomLoginView(LoginView):
    success_url = reverse_lazy('dashboard')

Parámetros perdidos o rutas incorrectas

Asegúrate de pasar todos los args o kwargs que la ruta requiere; si falta alguno, Django no sabrá cómo construir la URL y lanzará un NoReverseMatch.

Preguntas frecuentes sobre redirecciones en Django

  • ¿Cuál es la diferencia entre redirect() y HttpResponseRedirect?
    • redirect() es un atajo de alto nivel que acepta nombres de rutas, URLs relativas y absolutas, y decide internamente el tipo de respuesta. HttpResponseRedirect, en cambio, siempre devuelve un 302 y espera una URL explícita.
  • ¿Puedo redirigir con parámetros dinámicos?
    • Sí, usando kwargs directamente en redirect(), o bien combinando con reverse() para construir la URL con argumentos dinámicos de forma segura.
  • ¿Qué pasa con el SEO si cambio el slug de una URL?
    • Si no implementas una redirección 301, perderás el tráfico orgánico y los backlinks asociados a esa URL. En mi caso, aplicar el 301 correctamente fue la diferencia entre mantener las posiciones en Google o desaparecer.
  • ¿Cómo pruebo mis redirecciones antes de subir a producción?
    • Con curl -I <url> desde la terminal puedes ver el código de estado y la cabecera Location. En Google Search Console también puedes detectar redirecciones mal configuradas antes de que afecten a tu posicionamiento.

Conclusión

Las redirecciones en Django no son solo una herramienta técnica: son una aliada del SEO y de la experiencia de usuario cuando se usan correctamente.

En mis proyectos, aplicar bien los códigos 301 y 302 me ha permitido mantener el tráfico orgánico, evitar errores de duplicación de envíos y tener flujos más claros y predecibles.

Si planificas tus rutas, validas tus formularios y pruebas tus redirecciones antes de producción, te ahorrarás muchos dolores de cabeza. El patrón PRG es sencillo, pero marca la diferencia en apps que crecen.

El siguiente paso es continuar con nuestro CRUD y aprender a eliminar registros en Django.

Aprende a usar redirect() y HttpResponseRedirect en Django. Cómo aplicar el patrón PRG, hacer redirecciones 301/302, usar reverse() y evitar bucles infinitos.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.