The impact of Django page redirects to SEO - Fixing internationalized pattern URLs
Django makes heavy use of page redirects to handle form submissions, manage multilingual content and accomplish many other tasks. While there's nothing wrong with that (in fact in most cases it is considered to be the best practice), careless use of page redirects might affect our website's page rank. To better understand the potential danger we should first see how redirect works and which tools does Django provide for redirecting to a new page. We will also examine & fix a case where Django does not use the optimal redirect type: the internationalized URL patterns.
There are two kinds of redirects: permanent and temporary. A permanent redirect means that the content we try to access is no longer available through the current URL; a new URL exists and we should visit that instead. A temporary redirect means that the current URL is found, but we are temporarily moving to another page for some reason. In the latter case, future requests are expected not to generate a redirect.
Technically, on a permanent redirect the web server sends a 301 response code to the browser as opposed to 302 sent on temporary redirects. Therefore, the terms 301 redirect and 302 redirect are also used to describe permanent and temporary redirects. Django provides two response classes that generate redirect responses.
HttpResponseRedirect can be used for temporary redirects and
HttpResponsePermanentRedirect for permanently moved pages.
Redirects and SEO
SEO-wise each redirect type has a different effect to your website. When a page has moved permanently all links pointing to the old URL are considered as links to the new URL. Let's suppose we update an existing website and our product list (which was until now available at http://www.example.com/all-products-list/) has a new shiny page at http://www.example.com/products/. However all backlinks to our products page still point to http://www.example.com/all-products-list/ and we can't afford losing our links. If we use a 301 redirect search engines will treat all links to http://www.example.com/all-products-list/ as links to http://www.example.com/products/ and our problem will be solved. On the other hand, if we use 302 direct the link juice stays with the old link. This is also useful, but in a different scenario: Say we redirect our users to a thank you page (e.g. http://www.example.com/thank-you/) after they complete a purchase on our web store (e.g available at http://www.example.com/store/). Such a redirect should better not be permanent. The web store URL is valid and accessible and all links pointing to http://www.example.com/store/ must still point to that URL. In this case we must use a 302 redirect.
How Django uses redirects
We'll now see how Django uses redirects in two cases: form submission and multilingual page URLs.
Forms - a good use of redirects
When dealing with forms, it is always a good practice to use the generic class-based django FormView. It uses the Post/Redirect/Get pattern to temporarily redirect a user to a new page after a form is submitted. Your original URL is still accessible (all backlinks to that URL are also valid) so using the
HttpResponseRedirect class to redirect the user is the right thing to do.
Multilingual content with i18n_patterns - a bad use of redirects
Django 1.4 introduces i18n_patterns to enable internationalization in URL patterns. If you define a single URL (e.g. http://www.example.com/store/) you can have a working equivalent for all languages on your webpage (e.g. http://www.example.com/de/store/ will work for german translations, http://www.example.com/en/store/ is the the english version etc). This is really handy but what happens when a user requests the original store page found at http://www.example.com/store/? Django will redirect such a request to http://www.example.com/en/store/, if we assume your default language is english. To achieve this Django uses a temporary 302 redirect. That's not good because it splits your pagerank in two URLs. If we create a backlink to http://www.example.com/store/ it will not be considered as a link to http://www.example.com/en/store/. It will also impact search results as the same page will be listed twice.
Here's a quick fix to that problem. We'll create our own middleware that inherits its functionality from the original Django locale middleware and just uses a
HttpResponsePermanentRedirect instead of
HttpResponseRedirect. Create a new module named middleware.py and insert the following:
from django.conf import settings from django.core.urlresolvers import is_valid_path from django.http import HttpResponsePermanentRedirect from django.middleware.locale import LocaleMiddleware from django.utils import translation from django.utils.cache import patch_vary_headers class UpdatedLocaleMiddleware(LocaleMiddleware): def process_response(self, request, response): language = translation.get_language() if (response.status_code == 404 and not translation.get_language_from_path(request.path_info) and self.is_language_prefix_patterns_used()): urlconf = getattr(request, 'urlconf', None) language_path = '/%s%s' % (language, request.path_info) if settings.APPEND_SLASH and not language_path.endswith('/'): language_path = language_path + '/' if is_valid_path(language_path, urlconf): language_url = "%s://%s/%s%s" % ( request.is_secure() and 'https' or 'http', request.get_host(), language, request.get_full_path()) return HttpResponsePermanentRedirect(language_url) translation.deactivate() patch_vary_headers(response, ('Accept-Language',)) if 'Content-Language' not in response: response['Content-Language'] = language return response
All we have to do now is use that middleware instead of the original. In your project settings module locate the
MIDDLEWARE_CLASSES setting, insert the 'myapp.middleware.UpdatedLocaleMiddleware' and you're set.