How to add sitemap.xml to your Django Blog and make it work on Heroku (SEO)
Tags: django, heroku, sitemap, seo
Published: 2020-09-21 14:16:11

Okay, I've spent a little to much time to figure this out, but here's my version of the **Simplest Way to Add sitemap.xml to a Django App that is hosted on Heroku**. This will also solve the error: _"Internal Error 500"_ when trying to access /sitemap.xml from Heroku live app (production).

First things first, make sure you have what to serve as static or as dynamic links. In this example, I'll add to my sitemap.xml my dynamically created BlogPosts and 3 more static pages: Main/Index.html, About.html and Contact.html. So let's get started:

Firstly we need to add to our **settings.py** file the followings:
```python
# settings.py
INSTALLED_APPS = [
    ...

    'django.contrib.sitemaps',
    'django.contrib.sites',
]
SITE_ID = 1
```

Then, locally, we need to open our terminal and run python manage.py migrate. This will add to our local database the Sites model.
![Sites table](https://lh3.googleusercontent.com/C3AInAsgFAdAaP86bFDSOD6_qtn5BH79zckFBMhS7VbHjnyMDtNwf4YTgTD9vOPKgRyuVSRXhcBFrud5JBUl0NGIEJyeMgVr3Twb0noWmKxJttwFw20XD3Ty9FZRpEvMHQksFCihYK0=w2400)

We observe that the first entry in Sites table (with SITE_ID == 1) is "example.com"... We will later change that in production when we'll push to Heroku.

Now, we need to create a new file in our MainApp (Django App that we created at beginning of our project when we ran ./manage.py startapp MainApp), namely ../MainApp/**sitemaps.py**. Here we'll add the followings, we can also refer to the [documentation on Django Sitemaps](https://docs.djangoproject.com/en/3.1/ref/contrib/sitemaps/):

```python
# sitemaps.py
from django.contrib.sitemaps import Sitemap
from django.shortcuts import reverse
from .models import BlogPost

class BlogPostSitemap(Sitemap):
	changefreq = "weekly"

	def items(self):
		return BlogPost.objects.all()

class StaticViewSitemap(Sitemap):
	changefreq = 'monthly'

	def items(self):
		return ['index', 'about', 'contact']

	def location(self, item):
	    return reverse(item)
```

Now we need to add our continuingly generated sitemap.xml to our MainApp/**urls.py**. So we need to add these lines:
```python
# urls.py
from django.contrib.sitemaps.views import sitemap
from .sitemaps import BlogPostSitemap, StaticViewSitemap

sitemaps = {
    'posts': BlogPostSitemap,
    'static': StaticViewSitemap,
}

urlpatterns = [
    ...
    path('sitemap.xml', sitemap, {'sitemaps': sitemaps}),
]
```

Good. We can check that sitemap.xml is displayed in our local server by running _python ./manage.py runserver_ and access our _http://localhost:8000/sitemap.xml._

Aaand we are done! almost. Now we can finally push our code to Heroku, in the classic way:

```bash
> git status
> git add -A
> git commit -m "Add sitemap.xml to Django App"
> git push heroku master
```

But here's the catch... Now when we are trying to access _https://myappname.herokuapp.com/sitemap.xml_ we get an "INTERNAL SERVER ERROR (500)". Also, if we try to access our Sites model in our Admin page, we get the same error. 

But why's that?

When we pushed our code to Heroku, our heroku server calls the "makemigrations" command from our manage.py, but we didn't manually created (wrote any code) to our models.py file, so makemigrations will return _"No changes detected"_, therefore will not run "migrate" command.

So, we need to manually run migrate command from our heroku server. To do that, we simply call in our terminal these commands in our django project folder:
```bash
> heroku run bash
$ python manage.py migrate
```

And that's all. But now we also need to access again our admin page from our heroku deployed app, and change that first entry from Sites model from _"example.com"_ to _"myappname.herokuapp.com"_. Then, we'll have to run
```bash
> heroku restart
```

And now we are really done. We can access our sitemap from _myappname.herokuapp.com/sitemap.xml_.

I hope this mini-tutorial will help someone one day. It will certain help me to not struggle again if I encounter again this problem. 

Happy coding,

R.B.