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:
-
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
-
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:
- Keep the product models and related data in Django
- Expose this data through API urls
- 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
- Enhance the Django models with additional fields for structured content
- Update the API serializers to include these new fields
- Create reusable Astro components that match the design patterns
- 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