In the quest for a more streamlined and efficient web development workflow, I journeyed from Django to Astro.js. My initial platform,, served as both my blog and portfolio, but its cumbersome nature prompted me to consider separation, aiming for simplicity and speed.

Astro.js caught my attention for its unique approach to web development, blending the best of static site generation with server-side JavaScript. This framework's lack of a JavaScript runtime and its framework-agnostic nature were particularly appealing, aligning with my minimalist preferences. It promised a more straightforward, faster way to develop websites without sacrificing power or flexibility.

Django to Astro.js Here I go!

The transition from Django to Astro.js was surprisingly smooth. Astro's server-side templating language shares similarities with JSX, making the adaptation process almost seamless. The challenge was to convert Django template code into Astro's equivalent, a task that proved less daunting than anticipated. Here’s how I transformed a Django carousel component into an Astro component:

Django Template Code

A traditional Django component for a carousel featuring loops and conditional rendering within the template.

<div class="testimonial-carousel">
  <h2 class="testimonial-title" id="carouselExampleCaptions">Testimonials</h2>
  <div class="carousel-wrapper">
    <div class="carousel-container">
      <button class="carousel-control-prev" type="button" aria-label="Previous">&#10094;</button>
      <div class="carousel-inner">
        {% for item in carousel_items %}
        <div class="carousel-item {% if forloop.first %}active{% endif %}" data-index="{{ forloop.counter0 }}">
          <div class="carousel-item-content">
            <img class="avatar-img" src="{% static item.avatar_url %}" alt="avatar" />
            <div class="carousel-content">
              <div class="carousel-text">
                <h3 class="carousel-name">{{ }}</h3>
                <p class="carousel-position">{{ item.position }}</p>
                <p class="carousel-info">{{ }} - {{ item.year }}</p>
              <p class="testimonial-quote">{{ item.quote }}</p>
              <p class="carousel-review-link"><a target="_blank" rel="noopener noreferrer" href="{{ }}">Open
                  review on LinkedIn</a></p>
        {% endfor %}
      <button class="carousel-control-next" type="button" aria-label="Next">&#10095;</button>
<!-- Indicators -->
<ol class="carousel-indicators">
  {% for item in carousel_items %}
  <li {% if forloop.first %}class="active" {% endif %} data-carousel-index="{{ forloop.counter0 }}"></li>
  {% endfor %}

I also needed to migrate code from the Django view that provided the data for the carousel elements.

Django View Code

class PortfolioView(ListView):
    model = Post
    template_name = "blog/portfolio.html"  # <app>/<model>_<viewtype>.html
    context_object_name = "posts"  # The default is object_list
    paginate_by = 10

    def get_queryset(self):

    def get_context_data(self, *args, **kwargs):
        carousel_items = [
                "avatar_url": "portfolio/AmyBrazil.webp",
                "name": "Amy Brazil",
                "position": "Direct Manager",
                "company": "YellowfinBI",
                "year": "2022",
                "quote": "I had the pleasure to hire, onboard and manage John...",
                "link": "",
            # Additional Carousel Elements
        context = super().get_context_data(*args, **kwargs)
        context["carousel_items"] = carousel_items
        return context

Here is the final conversion of the template and view code combined into one .astro component. The conversion involved adapting Django's template syntax to Astro's JSX-like syntax, focusing on iterating over items and dynamically rendering components based on conditions.

.Astro component

import { Image } from "astro:assets";
import AmyBrazilAvatar from "/src/images/AmyBrazil.webp";
import CraigUtleyAvatar from "/src/images/CraigUtley.webp";
import MeredithBeanAvatar from "/src/images/MeredithBean.webp";
import KathrynThorpeAvatar from "/src/images/KathrynThorpe.webp";
import TaylorOshanAvatar from "/src/images/TaylorOshan.svg";

const carouselItems = [
		avatar: AmyBrazilAvatar,
		name: "Amy Brazil",
		position: "Direct Manager",
		company: "YellowfinBI",
		year: "2022",
		quote: "I'd hire him back in a heartbeat.",
	// Aditional Carousel Items

<script src="/src/scripts/carousel.js"></script>

<div class="text-center">
        class="text-3xl leading-none tracking-tight mb-3"
    <div class="relative">
        <div class="flex items-center justify-center">
                class="carousel-control-prev absolute left-14 top-1/2 transform -translate-y-1/2 text-4xl cursor-pointer"
            <div class="flex flex-col items-center">
          , index) => (
                            class={`carousel-item ${
                                index === 0 ? "block" : "hidden"
                            } data-index=${index}`}
                            <div class="mb-3 flex flex-col items-center">
                                    class="rounded-full shadow-lg mb-3 max-w-[150px] max-h-[150px]"
                                <div class="flex flex-col items-center gap-2 min-h-[160px]">
                                        <h3 class="font-normal font-xs leading-relaxed">
                                        <p class="font-bold text-lg leading-none">
                                        <p class="text-xs leading-relaxed">
                                            {} - {item.year}
                                    <p class="testimonial-quote max-w-[450px] break-words text-xl">
                <!-- The # of indicators matches the # of carousel items -->
                <ol class="flex justify-center gap-2">
              , index) => (
                            // Initialize the first indicator as active on page load
                                class={`w-2.5 h-2.5 cursor-pointer transition-colors duration-300 ease-in-out ${
                                    index === 0 ? "bg-accent" : "bg-gray-300"
                class="carousel-control-next absolute right-14 top-1/2 transform -translate-y-1/2 text-4xl cursor-pointer"

You can see that the syntax is very similar. Instead of injecting variables like this, `{{ item.position }}you simply do it with a single curly brace, { item.position}.


Migrating to Astro.js from Django has been a revelation in web development simplicity and efficiency. The ease of integrating server-side and client-side logic in Astro components, combined with its impressive documentation, has made this transition a rewarding experience. For developers considering a shift to a more streamlined, statically-generated approach to web development, Astro.js offers a compelling pathway, echoing familiar paradigms while introducing powerful new capabilities.

Instead of running a server, my portfolio is hosted statically in S3. I'm paying a quarter a month to host and serve my site. That's a bargain!

Until next time!


PR removing portfolio from Django Blog

New Portfolio Page

New Astro.js Github Repo


Back to Home
John Solly Profile Picture
John Solly Profile Picture

John Solly

Hi, I'm John, a Software Engineer with a decade of experience building, deploying, and maintaining cloud-native geospatial solutions. I currently serve as a senior software engineer at New Light Technologies (NLT), where I work on a variety of infrastructure and application development projects.

Throughout my career, I've built applications on platforms like Esri and Mapbox while also leveraging open-source GIS technologies such as OpenLayers, GeoServer, and GDAL. This blog is where I share useful articles with the GeoDev community. Check out my portfolio to see my latest work!