C
Corrison

Using Astro and Django Architecture

Using Astro and Django Architecture

When I first started building Corrison it was a Django only project. I decided to change the way I was going with it so I can reuse it over and over using APIs. I had to transition from a traditional Django site to a headless architecture using Django REST Framework for the API and with Astro as the frontend.

I chose Astro as my frontend framework because it was fairly simple to use, renders content quickly and is SEO friendly. Plus, I can use other frameworks like Vue and Sveltekite with it.

I started by thinking about my content strategy. There are two main approaches I use when building sites. A content-heavy approach with Django and using Astro as a template engine or using Django API and the page layouts and elements in Astro.

Content Strategy

There are two main approaches to consider:

  1. Content-Heavy in Django Models:

    • Store most content structure in your Django models
    • Use Astro primarily as a renderer/template engine
    • Best for: Sites with complex content requirements, need for admin editing, and dynamic content
  2. Static Structure in Astro:

    • Keep page layouts and common elements in Astro components
    • Use Django primarily as a data API
    • Best for: Performance-focused sites with mostly static layouts

Corrison API has been built using the first approach.

How I Integrated Django Data into Astro Templates

One of the key challenges I faced was displaying Django data within my Astro components. Here's how I handled fetching and rendering Django API data in Astro:

How I Fetched Data from Django API

In my Astro pages and components, I fetched data from my Django API in the frontmatter section:

---
// /src/pages/blog/[slug].astro
const { slug } = Astro.params;

// Fetch blog post data from Django API
const response = await fetch(`${import.meta.env.API_BASE_URL}/api/blog/${slug}/`);
const blog = await response.json();
---

How I Used Django Data in Astro Templates

Once I had the data, I used it throughout my Astro templates using curly braces {}, similar to how I'd used variables in Django templates:

<article class="max-w-4xl mx-auto px-4 py-8">
  <header class="mb-8">
    <h1 class="text-4xl font-bold text-gray-900 mb-4">
      {blog.title}
    </h1>
    <div class="flex items-center text-gray-600 mb-4">
      <span>By {blog.author.name}</span>
      <span class="mx-2">·</span>
      <time>{new Date(blog.created_at).toLocaleDateString()}</time>
    </div>
    {blog.featured_image && (
      <img 
        src={blog.featured_image} 
        alt={blog.title}
        class="w-full h-64 object-cover rounded-lg"
      />
    )}
  </header>
  
  <div class="prose prose-lg max-w-none">
    <Fragment set:html={blog.content} />
  </div>
  
  {blog.tags && blog.tags.length > 0 && (
    <footer class="mt-8 pt-4 border-t">
      <div class="flex flex-wrap gap-2">
        {blog.tags.map(tag => (
          <span class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm">
            {tag.name}
          </span>
        ))}
      </div>
    </footer>
  )}
</article>

What I Learned About Using Django Data in Astro:

  • Direct Property Access: I used {blog.title}, {blog.content}, {blog.author.name} to access Django model fields
  • Conditional Rendering: I used {blog.featured_image && (...)} to conditionally show elements
  • HTML Content: I used <Fragment set:html={blog.content} /> to render rich HTML content from Django
  • Arrays/Lists: I used .map() to iterate over Django relationships like {blog.tags.map(tag => ...)}
  • Date Formatting: I converted Django datetime strings using new Date(blog.created_at).toLocaleDateString()

How I Built Product Pages (E-commerce Example):

---
const { slug } = Astro.params;
const response = await fetch(`${import.meta.env.API_BASE_URL}/api/products/${slug}/`);
const product = await response.json();
---

<div class="product-detail">
  <h1>{product.name}</h1>
  <p class="price">${product.price}</p>
  <div class="description">
    <Fragment set:html={product.description} />
  </div>
  
  {product.images && (
    <div class="image-gallery">
      {product.images.map(image => (
        <img src={image.url} alt={product.name} />
      ))}
    </div>
  )}
  
  <div class="specifications">
    <h3>Specifications</h3>
    <ul>
      {product.specifications.map(spec => (
        <li>
          <strong>{spec.name}:</strong> {spec.value}
        </li>
      ))}
    </ul>
  </div>
</div>

This approach allowed me to use my Django models' data in Astro while maintaining the performance benefits of static site generation.

Create Individual Pages

I created standalone pages (like ecommerce, blog, etc.), directly in the /src/pages/ folder. For example:

  • /src/pages/ecommerce.astro
  • /src/pages/blog.astro

These will be accessible at /ecommerce and /blog URLs.

Understanding Astro's File-Based Routing

In Astro, the file structure determines your routes:

  • /src/pages/index.astro → /
  • /src/pages/about.astro → /about
  • /src/pages/services.astro → /services
  • /src/pages/blog/index.astro → /blog
  • /src/pages/blog/[slug].astro → /blog/any-post-slug

So standalone pages are created directly in the /src/pages/ folder. Collection-based pages (like Blog posts) that share a template, use the folder + [slug].astro pattern.

Fix the Navigation in Header.astro

I had to update the Header component to point to the correct routes:

---
const links = [
  { href: "/", label: "Home" },
  { href: "/blog", label: "Blog" },
  { href: "/pages", label: "Pages" },  // This should point to /pages
  { href: "/linkhubs", label: "Links" }, // This should point to /linkhubs
];
---
<header class="bg-white shadow-md">
  <nav class="container mx-auto p-4 flex space-x-4">
    {
      links.map((link) => (
        <a href={link.href} class="text-gray-700 hover:text-blue-600">
          {link.label}
        </a>
      ))
    }
  </nav>
</header>

For E-commerce Section

When adapting the Django e-commerce templates to Astro I had to:

  1. Keep the product models and related data in Django
  2. Expose this data through API urls
  3. Create Astro components that mimic the existing templates, but consume data from the API

For example, the product detail page in Astro would:

  • Call the Django API to fetch product data. Add an API endpoint that returns the same complete product data with the templates.
  • Render that data using a similar layout to the existing Django template
  • I split them into smaller components (ProductGallery, ProductInfo, ProductTabs)
  • Use client-side JavaScript for interactivity (like the image gallery and tabs)

Implementation Process

  1. Enhance the Django models with additional fields for structured content
  2. Update the API serializers to include these new fields
  3. Create reusable Astro components that match the design patterns
  4. Build Astro pages that consume the API data

This approach gives me:

  • Content management through the Django admin
  • Performance benefits of Astro
  • Reuse of my existing e-commerce logic
  • Flexibility to evolve the design separately from the content

Doing this helped me create a more maintainable and flexible system.

 

Developer : Diane Corriette @ djangify.com 

 

https://www.linkedin.com/in/todianedev/