We love designing and developing websites, but what really drives us is solving problems and cultivating strong relationships with our clients
Building SEO optimised Django web applications
By : shabda
This is an article about building SEO, optmised web applications with Django. I assume you already have an idea how Seo works. We will take the example of a hypothetical Blog app to explain the suggestions made.
Without further ado, here are the tips.
Basic
- Use beautiful URLs.
- Learn the difference between HTTP 301 and HTTP 302 redirects.
- Layout content in a SEO friendly way.
- Create a sitmaps.
Not so obvious
- Use the Admin to allow end users to update fields.
- Do not get your Url structures too deep.
- Create a robots.txt file.
- Link to your pages wisely.
- Minimise duplicate content.
- Ping Google when your site gets updated.
Basic tips.
If you are building a production web app, you should really be using these techniques.
Use beautiful URLs.
Say our app has a model like,
class Entry(models.Model):
title = models.CharField(max_length = 100)
body = models.TextField()
It would be very tempting to have Urls like /entry/15/, and map this url to the entry with PK 15. Resist this temptation. Search Engines need help to decide what a page is about, and the Urls plays a major part in that. Instead we want the Url to depend on the title.
class Entry(models.Model):
title = models.CharField(max_length = 100)
body = models.TextField()
slug = models.SlugField()
def save(self):
self.slug = '-'.join(self.title.split())#And clean title, and make sure this is unique.
super(Entry, self).save()
Then we can use slug, and have Urls like /entry/seo-tips-for-django/
Read more:
[1]: Dynamic URLs vs. Static URLs
Learn the difference between HTTP 301 and HTTP 302 redirects.
A HTTP 301 redirects says that the url currently being accessed has moved permanenetly, while a HTTP 302 is temporary redirect which asks search engines to try the same url next time. In Django a Http 301 maps to HttpResponsePermanenetRedirect, and a HTTP 302 maps to HttpResponseRedirect. A 301 redirect makes the search engines count the links pointing to a old Url for the new Url.
Suppose our old Url was /entry/seo-tips-for-django/, and we edited the title to "Best SEO tips for Django", and want the new url to be /entry/best-seo-tips-for-django/. Now as "Cool URIs Do not change", we want to do an redirect from /entry/seo-tips-for-django/ to /entry/best-seo-tips-for-django/. As some people may have already linked to /entry/seo-tips-for-django/, we want a HttpResponsePermananentRedirect here.
Layout content in a SEO friendly way.
Django gives you complete control over how your Html will appear. Use this to your advantage.
Search engines give more weight to the content which appears earlier in a page layout. So layout your page where the the content area appears before the footer, and then layout your pages using CSS.
For example the first base template is better than second,
<body>
{% block content %}
{% endblock %}
{% block sidebar %}
{% endblock %}
{% block footer %}
{% endblock %}
</body>
<body>
{% block footer %}
{% endblock %}
{% block sidebar %}
{% endblock %}
{% block content %}
{% endblock %}
</body>
Create a sitmaps.
Django comes with a wonderful Sitemaps framework. Use this to generate a Sitemap for all our dynamic pages.
Here is the all the code you need to generate the sitemap for our Entries, taken directly from Django Sitemaps page, and it works perfectly. Plus this is your urls.py and you have a shiny xml sitemap ready.
Read More
[1]: Site Map
Use the Admin to allow end users to update fields.
Use the wonderful Admin interface to your advantage. For example, for /entry/seo-tips-for-django/ we can use the Admin to allow users to update the slug without updating the title.
Do not get your Url structures too deep.
Search Engines give more weight to pages which are closer to the root of the site. Instead of /blog/entry/2008/oct/2008/seo-tips-for-django/, prefer /entry/seo-tips-for-django/. As Django makes including urlconfs inside other urlconfs so easy, this can lead to deeper Url structure than needed.
For example your Django project has a main urls.py,
which does,
url(r'^blog/', include('blog.urls')),
and blog/urls.py does
url(r'^search/', include('blog.searchurls')),
and blog/urls.py does
url(r'^search/$', 'search_view'),
then your serach page will be at /blog/search/search/, when it should probably be at /search/. Include with care.
which includes blog/urls.py
Create a robots.txt file.
Use Django Robots to create robots,txt file for your Django site.
Link to your pages wisely.
Eg. When you are linking to the the entry page from main page/other page on your site use, use descriptive anchor texts. SO the permalink for our entry pages shuld not be,
<a href="{{ entry.get_absolute_url }}">Read More</a>
But rather,
<a href="{{ entry.get_absolute_url }}">{{ entry.title }}</a>
Minimise duplicate content.
Search engines hate duplicate content. So try not to have the same content on multiple Urls.
For example see this innocous looking url pattern
urlpatterns = patterns('',
(r'^entry/(\w+)/', 'blog.view.entry'),
...
This url pattern will match for all of /entry/seo-tips-for-django/1, /entry/seo-tips-for-django/2, /entry/seo-tips-for-django/3 and similar.
To make sure only /entry/seo-tips-for-django/ matches, use
urlpatterns = patterns('',
(r'^entry/(\w+)/$', 'blog.view.entry'),
...
On a similar note, say you have commenst for your Blog, which you want permalinks for.
Do not do this.
class Comment(models.Model):
def get_absolute_url(self):
#You would use permalink here.
return '/comment/%s/' % self.id # or even '/comment/%s/' % self.slug
Instead what you want here is,
class Comment(models.Model):
def get_absolute_url(self):
return '%s#%s' % (self.entry.get_absolute_url(), self.id)
And create named anchors in your entries template. This makes sure that each comment is visible at only one Url.
Ping Google when your site gets updated.
You would have created a Dynamic sitemap using the sitemaps framework. SO when your content gets updated, you sitemap would get updated too. Let Google know of this using django.contrib.sitemaps.ping_google
from django.contrib.sitemaps import ping_google
class Entry(models.Model):
# ...
def save(self, force_insert=False, force_update=False):
super(Entry, self).save(force_insert, force_update)
try:
ping_google()
except Exception:
# Bare 'except' because we could get a variety
# of HTTP-related exceptions.
pass
http://docs.djangoproject.com/en/dev/ref/contrib/sitemaps/#pinging-google
Need a web application built? Talk to us.
Comments
The redirect_to generic view (Django 1.1 alpha 1) now accepts an additional keyword argument permanent. If permanent is True, the view will emit an HTTP permanent redirect (status code 301). If False, the view will emit an HTTP temporary redirect (status code 302).
1.1 alpha 1 release notes
In 'save' method (on 'Entry' class) you can use 'slugify' method from 'django.template.defaultfilters'.
So, instead of:
self.slug = '-'.join(self.title.split())
you can write like this:
self.slug = slugify(self.title)
I think it's much more simplified and easy to use :-)
Florian, try django.http.HttpResponsePermanentRedirect. There are a few more in django.http that can be of use too.
Thanks for the tips. :) I got a regex question though:
shouldn't the slug url be (r'^entry/([\w-]+)/$', 'blog.view.entry') instead? \w matches only digits, letters and underscores, not hyphens.
Great article, I'm working on my first Django app and I plan on using most of these tips.
I do think in tip #4 you mean sitemaps, correct?
- How to use pep8.py to write better Django code
- Screencast: Django Tutorial Part 1
- How and why to use pyflakes to write better Python
- Getting started with South for Django DB migrations
- A brief overview of Vagrant
- Writing jQuery plugins using Coffeescript
- Behind the Scenes: Request to Response
- Using SQLite Database with Android
- Haml for Django developers
- Coffeescript for Python programmers
- rails
- django
- linkroundup
- django opinion
- opinion
- business
- API
- appengine
- python
- satire
- startup
- Uncategorized
- marketing
- personal
- rambling
- search
- interviews
- seo-interviews
- 5startupideas
- ideas
- seo
- tips
- forms
- paypal
- utilities
- datetime
- web2.0
- Amazon
- algorithms
- presentations
- products
- pinax
- satchmo
- ecommerce
- microsoft
- yahoo
- book
- tutorial
- models
- aggreagtion
- meta
- India
- apps
- about
- CSS
- Design
- wordpress
- test slug
- vim
- urls
- reviews
- javascript
- xmpp
- emacs
- Typography
- Grid Theory
- Color Theory
- iphone
- android
- titanium
- mobile applications
- CSS3
- Browser Compatibility
- mobile
- jobs
- lamson
- django setup
- files
- upload
- jsTree
- hierarchical view
- web page
- Treeview
- coffeescript
- request
- response
- South
- django south
- django migration
- --fake
- screencasts
- February 2012
- January 2012
- December 2011
- October 2011
- September 2011
- July 2011
- June 2011
- April 2011
- February 2011
- January 2011
- December 2010
- November 2010
- October 2010
- September 2010
- June 2010
- April 2010
- March 2010
- January 2010
- December 2009
- November 2009
- October 2009
- September 2009
- August 2009
- July 2009
- June 2009
- April 2009
- March 2009
- February 2009
- November 2008
- October 2008
- June 2008
- May 2008
- April 2008
Thanks for the great article!
But where do you get HttpResponsePermananentRedirect from?