Django

I Built My Django Portfolio, Then Realized Building It Wasn't Enough

Deploying my Django portfolio wasn't the end of the project. I also needed search engines and social platforms to understand it. Here's how I added the SEO foundations to my Django application.

Syed Saddam Shah
September 13, 2026
10 min read

Building and deploying my Django portfolio felt like a major milestone.

The pages worked.

My projects were there.

The blog was running.

The website was live.

But that raised another question:

How will search engines actually understand what is on the website?

A working website and a discoverable website are not necessarily the same thing.

That led me to work on another part of the project that isn't as visually obvious as building pages or fixing deployment errors:

technical SEO.

Rather than treating SEO as something separate from development, I started implementing some of its foundations directly inside my Django application.

The Problem: A Browser Can Read My Site, but What About a Search Engine?

A visitor can navigate a portfolio naturally.

They see navigation such as:

Home
├── About
├── Services
├── Projects
├── Resume
├── Blog
└── Contact

They understand what those pages mean because of the interface.

A search engine doesn't interact with the site exactly like a human visitor.

It needs signals that help it discover URLs and understand what individual pages contain.

My portfolio also introduced another challenge: the blog.

Unlike a small static portfolio containing only a handful of fixed URLs, blog content creates individual article URLs.

Conceptually, the website now looks more like:

saddamshah.com/
│
├── about/
├── services/
├── projects/
├── resume/
├── contact/
│
└── blog/
    ├── article-one/
    ├── article-two/
    └── future-articles/

As I publish more content, manually thinking about every URL becomes increasingly impractical.

I wanted Django to help manage this.

Step 1: Creating a Sitemap with Django

Django includes a sitemap framework, so I could generate the sitemap from the application instead of manually maintaining an XML file.

I added Django's sitemap functionality to the project:

INSTALLED_APPS = [
    # ...
    "django.contrib.sitemaps",
]

Then I separated the URLs into sitemap groups.

My project uses:

sitemaps = {
    "static": StaticViewSitemap,
    "blog": BlogSitemap,
}

This distinction is useful.

The portfolio contains two different kinds of content.

Static pages

These are URLs such as:

/
/about/
/services/
/projects/
/resume/
/contact/

Their routes are relatively stable.

Dynamic blog content

Blog articles are different.

Every new published article can create another URL.

Instead of manually editing the sitemap whenever I publish something, the sitemap can be generated from the application's content.

That is one of the advantages of handling SEO at the application level.

Step 2: Exposing sitemap.xml

After defining the sitemap configuration, I connected it through Django's URL routing.

The relevant route looks like this:

from django.contrib.sitemaps.views import sitemap

urlpatterns = [
    # ...

    path(
        "sitemap.xml",
        sitemap,
        {"sitemaps": sitemaps},
        name="sitemap",
    ),
]

Now the application can expose:

/sitemap.xml

instead of requiring me to manually create and update the XML document.

Conceptually, the sitemap provides search engines with something like:

<urlset>
    <url>
        <loc>https://saddamshah.com/</loc>
    </url>

    <url>
        <loc>https://saddamshah.com/projects/</loc>
    </url>

    <url>
        <loc>https://saddamshah.com/blog/...</loc>
    </url>
</urlset>

The important part for me wasn't simply generating XML.

It was making URL discovery part of the Django application itself.

When the site's content grows, the infrastructure can grow with it.

Step 3: Adding robots.txt

I also wanted the site to give web crawlers basic instructions.

For this project, I implemented a small Django view that returns the robots configuration as plain text:

from django.http import HttpResponse


def robots_txt(request):
    content = """User-agent: *
Allow: /

Disallow: /admin/

Sitemap: /sitemap.xml
"""

    return HttpResponse(
        content,
        content_type="text/plain"
    )

Then I exposed it through the URL configuration:

path(
    "robots.txt",
    robots_txt,
    name="robots"
)

This gives the website:

/robots.txt

The response communicates three basic instructions:

User-agent: *
Allow: /

Disallow: /admin/

Sitemap: /sitemap.xml

The intention is straightforward.

Public website content can be crawled, the Django admin isn't intended as public content, and crawlers are given the location of the sitemap.

Sitemap vs. Robots.txt

Initially, it can be easy to think these files do the same job.

They don't.

I think about them like this:

              SEARCH ENGINE
                    │
           ┌────────┴────────┐
           │                 │
      robots.txt         sitemap.xml
           │                 │
    Crawling guidance      URL discovery
           │                 │
           └────────┬────────┘
                    │
                 WEBSITE

robots.txt provides crawler instructions.

sitemap.xml helps expose the URLs I want search engines to discover.

They complement each other.

Step 4: Making Page Metadata Flexible

URL discovery is only part of SEO.

Search engines also need context about individual pages.

A portfolio has pages with very different purposes.

For example:

Home      → Developer positioning
Projects  → Development work
Resume    → Skills and experience
Blog      → Technical content
Article   → One specific technical topic

Using exactly the same page title and description everywhere would throw away useful context.

Django template inheritance provides a clean way to handle this.

The base template can define defaults:

<title>
    {% block title %}
        Saddam Shah | Full-Stack Django Developer
    {% endblock %}
</title>

Individual pages can then override the value:

{% block title %}
Projects | Saddam Shah
{% endblock %}

The same principle can be used for descriptions.

<meta
    name="description"
    content="{% block meta_description %}
    Full-Stack Django Developer building web applications,
    APIs, and backend systems.
    {% endblock %}"
>

Then a specific article can provide metadata relevant to that article rather than inheriting a generic portfolio description.

This is where Django's template system becomes particularly useful.

I don't need to duplicate the entire HTML <head> across every template.

I define the structure once and override the information that changes.

Step 5: Thinking Beyond Google

There is another place where page metadata matters:

social sharing.

When somebody shares a portfolio page or article, platforms may use metadata to determine the preview.

That includes information such as:

<meta property="og:title"
      content="Page Title">

<meta property="og:description"
      content="Page description">

<meta property="og:type"
      content="website">

<meta property="og:image"
      content="...">

For larger previews, Twitter/X metadata can follow the same idea:

<meta name="twitter:card"
      content="summary_large_image">

<meta name="twitter:title"
      content="Page Title">

<meta name="twitter:description"
      content="Page description">

This was especially relevant for my blog.

I'm publishing technical articles and sharing them outside the website, including on LinkedIn.

That means the presentation of an article shouldn't begin only after someone clicks it.

The shared preview is part of the experience too.

Dynamic Content Changes the SEO Problem

The blog made me think about SEO differently.

With only five or six static pages, maintaining metadata manually is manageable.

But imagine the site after publishing:

10 articles
25 articles
50 articles
100 articles

Each article potentially has:

Title
Description
Slug
Featured image
Category
Content
URL

At that point, content management and SEO start overlapping.

The better question becomes:

How can the application use information it already has to generate meaningful metadata?

For example, conceptually:

<title>
    {{ post.seo_title|default:post.title }}
</title>

<meta
    name="description"
    content="{{ post.meta_description }}"
>

Now SEO information can follow the article rather than requiring changes to the global template every time new content is published.

That is a much more scalable approach.

What This Changed About How I Think About Django

When I first started building Django projects, most of my attention naturally went toward things like:

Models
Views
URLs
Templates
Forms
Databases
Authentication

Those are fundamental.

But building an actual website pushed me to think about another layer:

              Django Application
                      │
       ┌──────────────┼──────────────┐
       │              │              │
 Application       Production     Discoverability
    Logic          Deployment          │
       │              │          ┌─────┴─────┐
 Models/Views     Configuration   Sitemap  Metadata
                                  Robots   Social

A production website isn't just application logic.

It also has to communicate properly with systems outside the application.

Search engines are one of those systems.

What I Learned

The biggest lesson wasn't that every Django project needs a few SEO tags copied into its <head>.

It was that SEO can be treated as part of application architecture rather than an afterthought.

Django already knows:

  • which URLs exist,
  • which blog posts exist,
  • what their titles are,
  • what their slugs are,
  • and what content is published.

Using that information programmatically is much better than maintaining everything manually.

I also learned that deploying a website isn't necessarily the finish line.

After asking:

"Does the website work?"

there are other useful questions:

Can users find it?

Can search engines discover its pages?

Does each page explain what it contains?

What happens when an article is shared?

Can the SEO structure grow as the content grows?

Those questions pushed my portfolio beyond simply being a collection of Django templates.

Building for More Than the Browser

My portfolio started as a way to demonstrate my development skills.

But building it as a real deployed application continues to expose areas that small local projects don't always force you to think about.

First came deployment.

Then production debugging.

Then external integrations.

And now discoverability.

Each layer has made the project more useful as a learning experience.

The goal isn't just to build something that renders correctly when I open it in my browser.

It's to understand the different systems a production web application needs to work with.

And that is becoming one of the most valuable parts of building the project.


I'm documenting the real technical decisions, problems, and lessons behind building my Django portfolio rather than only showing the finished UI.

You can explore the project at saddamshah.com.

Tags
#Django #Python #Web Development #SEO #Sitemap #Portfolio

Enjoyed This Article?

If you're looking for a Django developer to build secure web applications, REST APIs, or scalable backend systems, I'd be happy to discuss your project or the next opportunity.