En

How I built my blog in 2026 using a modern architecture

2026-06-20 12 min read
Carlos Castañeda
Carlos Castañeda @carloscndev

For a long time, I have been tempted to create my own website: a personal space to share my knowledge, projects, future plans, hobbies, and any random idea that crossed my mind.

Sure, social media covers almost every aspect of our lives nowadays, but the idea here is different. I wanted a personal portal where I could set the mood and the tone, where I could publish whatever I wanted and let the content dictate the format, whether it was text, images, videos, or anything else.

With that in mind, I set a few important goals for the site:

  • Decoupled data: If I ever decide to completely redesign the site, the content shouldn't be affected.
  • Performance: Since it’s a personal site with controlled content, it needs to load fast and stay stable as it grows over time.
  • Scalability: I have no idea what new sections I might want to add in the future, so the architecture must allow me to grow without too much hassle.
  • Internationalization: It was important for me to publish content in both English and Spanish to reach a wider audience.

With these requirements clear, the technical decisions started taking shape. The architecture that fit best was JAMstack: a decoupled frontend, a CMS to manage content, and an automated pipeline to deploy changes. After researching various options and running some proof-of-concepts, these are the technologies I chose:

  • Astro for the frontend. It’s specifically designed for content-focused sites like blogs, documentation, or marketing pages, with a strong emphasis on performance.
  • Strapi  as a headless CMS. It allows me to manage content independently from the frontend and expose it via APIs. Plus, it has built-in support for internationalization, which made it easy to handle English and Spanish versions from the start.
  • GitHub Actions to automate testing and deployments. I wanted a basic validation layer before publishing changes to avoid unnecessary errors.

These are the technologies I chose to build the site, but this was just the first step. Another equally important decision was choosing where to host each component. I had to consider several factors: costs, traffic, scalability, ease of integration, and above all, convenience. At the end of the day, no one wants to manage a site where making a simple change becomes a complicated task. So, in the following sections, I’ll talk about how I addressed each of these concerns.

How I got my content and frontend to communicate seamlessly

Once the technologies were defined, the next step was to model the content in Strapi and connect it to Astro.

Data Architecture: Preparing the ground to scale

Strapi organizes content using three main concepts:

  • Single Types: Content that exists only once in the project, like global site configuration or the main navigation.
  • Collection Types: Sets of records with the same structure. They are equivalent to database tables and are used for information that needs to be related or uniquely identified, such as blog posts, projects, or authors.
  • Components: Reusable blocks that allow you to group fields and reuse structures across different parts of the site, similar to React components.

Within components, there are two useful variants:

  • Repeatable: Allows for multiple instances of the same block, such as navigation items or links.
  • Dynamic: Allows you to build pages by combining different blocks in various orders.

Based on this, I modeled the site's information as follows:

Collection Types

  • Author: Represents the blog's authors.
  • Blog Post: The actual entries.
  • Job: Professional experiences throughout my career.
  • Project: Personal or professional projects, including this very site.

Components

  • SEO: Metadata for search engines.
  • Navigation: Site navigation configuration.
  • Link: Represents a link within the interface.

Single Types

Main pages like Home or About are modeled as Single Types, as there is only one instance of each. Every page combines its own content with information coming from collections and components. This structure will likely evolve over time, but for now, it's flexible enough to grow without complicating the administration.

Integrating with Astro: The final piece

One of the project's main goals was to build a site that was fast, easy to deploy, and scalable without too much complexity. That’s why I decided the final output should be a static site.

During the build process, Astro fetches all the information from Strapi and generates static HTML files, which are then served via a CDN or any modern provider. To better understand this decision, it’s worth comparing both approaches:

Feature Static Site (JAMstack) Dynamic Site (SSR)
Content generation During build On each request
Data source APIs / Headless CMS Direct database
Speed Very high (CDN) Depends on the server
Updates Requires rebuild Immediate
Costs Low Higher
Security Smaller attack surface Higher complexity

Although plugins exist to integrate Strapi with Astro, I opted for a simpler solution in this project: a small client using fetch and the qs library.

The basic flow is as follows:

graph TD A[Strapi: CMS] --> B[Data Modeling] B --> C[Astro: Fetch data] C --> D[Astro: Build process] D --> E[Result: Static Site]

Each page pulls only the data it needs during the build process. The home page might load global configuration and featured content, while the blog queries specific posts. The key takeaway is that these queries happen at compile time, not at runtime, which allows the final result to be a fully static, lightning-fast site.

The challenge of multilingualism without compromising performance

Internationalization (i18n) for static sites can be approached in several ways, and the choice directly impacts the project's architecture. In general, there are three common strategies:

1. Static Routing (Language-based paths)

This is likely the most common approach in modern frameworks. Separate routes are generated for each language, for example:

  • /es
  • /en

Each route corresponds to a completely independent version of the site.

Pros:

  • Simple to implement: It’s straightforward to set up.
  • Native compatibility: Fits naturally into the JAMstack/static site model.
  • CDN-friendly: Allows for full per-language caching at the edge.
  • No runtime overhead: Avoids complex logic in the browser or server.

Cons:

  • Structural duplication: Page structures are duplicated for each locale.
  • Full page reloads: Switching languages requires a complete page navigation.
  • Build size: Can lead to a larger total build size as the site grows.

2. Client-side i18n (Runtime translation)

In this approach, content is translated or resolved dynamically in the browser or on the server.

Pros:

  • Full runtime flexibility: Allows for dynamic content resolution on the fly.
  • Efficient builds: No need to trigger separate build processes for each language.
  • Dynamic-first: Ideal for highly interactive, data-heavy applications.

Cons:

  • Loss of static benefits: Misses out on the performance gains of pure static rendering.
  • Frontend complexity: Increases the logic and state management required on the client side.
  • Resource heavy: Often requires additional network requests or larger JavaScript bundles.

3. The chosen approach

This is the approach I used for this project. During the build process in Astro, the system performs a full data fetch for each language. In other words, each language is resolved at compile time, not at runtime.

This means that both Spanish and English content are generated as part of the same build process, but as separate datasets.

Why this approach?

The decision to use build-time i18n comes directly from the project's core philosophy:

  • Keep the site completely static.
  • Avoid complexity at runtime.
  • Optimize performance and caching.
  • Centralize logic within the build process.

In other words: I preferred to move the complexity to the build stage rather than pushing it onto the user.

graph TD Strapi[Strapi CMS] -->|Fetch ES| Build[Astro Build Process] Strapi -->|Fetch EN| Build Build -->|Generates Static Files| Static[Static Site] Static -->|Language Switch| Render[DOM Node Swap]

CI/CD and the "laziness" of generating new versions

One of the most important goals of this project was to keep friction to an absolute minimum. If I change something in the CMS, I want the site to update almost by itself. I don't want to connect to servers or run commands manually. I want to write content, hit publish, and go on with my day.

As I’ve mentioned, the result of the Astro build is a completely static site. There are several services to host these, like AWS S3 or Google Cloud buckets; however, I ultimately chose Vercel for these reasons:

  • GitHub Integration: Seamless workflow.
  • Multi-environment support: Easy staging and production handling.
  • Generous free tier: More than enough for my needs.
  • Webhook support for Strapi: Perfect for automated triggers.

Unlike Astro, which generates a static site, Strapi is a Node.js-based application, meaning it needs a server to run and expose its API. However, the traffic pattern is quite different from the frontend. In practice, Strapi isn't directly exposed to end users; it’s mostly used by me as the author, keeping the load well-controlled.

For this reason, I didn't need a complex or highly scalable infrastructure—just something simple, stable, and easy to maintain. That’s where Railway comes in. It’s a hosting platform designed for backend apps that feels just as simple to use as Vercel.

What makes it great:

  • Direct GitHub integration: Automates deployments without manual setup.
  • Consumption-based pricing: I only pay for what I use.
  • Easy multi-environment management: Dev vs. production is a breeze.

Together, this makes it a very natural choice for hosting a CMS like Strapi within a lightweight architecture.

Great, but how do I update the frontend when Strapi changes?

One of the issues with static sites is that content doesn't update automatically. Each change in the CMS requires a site rebuild. Fortunately, Vercel offers  Deploy Hooks. The flow is simple:

graph LR Strapi[Strapi CMS] -->|Webhook| Vercel[Vercel Deploy Hook] Vercel --> Build[Astro Build] Build --> Site[Updated Static Site]

Every time I publish or update content in Strapi, it sends a webhook to Vercel, which automatically triggers a new build. This works especially well with multiple environments, allowing me to test changes before they go live.

With this, the final architecture of the site looks like this:

Fragmento de código

graph TD A[Author / Editor] --> B[Strapi CMS] B --> C[Content + Media + i18n] B -->|Webhook| D[Vercel Deploy Hook] D --> E[Build on Vercel] E --> F[Astro generates static site] F --> G[Vercel CDN] G --> H[Users]

Scalability and Infrastructure

One of the fundamental pillars of system design is scalability: the ability to support growth without needing to redesign the entire architecture.

In this project, scalability is resolved primarily through the static site approach. Once the build is generated, the result doesn't depend on servers rendering content in real-time. Instead, the site becomes a set of static files distributed via a CDN. This completely changes the scalability model: instead of scaling compute, you simply scale distribution.

Vercel handles this global delivery layer automatically, which means I didn't need to configure load balancers, additional servers, or complex scaling strategies.

Security and Access Control

Another important pillar is security, understood not just as active protection, but as a reduction of the attack surface. In this architecture, the separation of concerns plays a key role:

  • The frontend is completely static and public.
  • The CMS (Strapi) is isolated and only accessible to administrators and collaborators.
  • Content is exposed only through controlled APIs.

This model significantly reduces critical entry points, as there is no database directly exposed to the user and no server rendering logic in real-time.

Performance and Optimization

The third pillar is performance, which is resolved by the architecture's design. By serving static content from a CDN, load times are minimized because files are distributed from nodes closest to the user. This eliminates the need for real-time page generation or additional backend queries.

In the current state of the project, media content lives directly within Strapi, which simplifies the initial architecture. However, if the project were to grow significantly, some natural optimizations could be implemented:

  • Moving media content to a dedicated bucket (like S3 or similar services).
  • Introducing more aggressive caching strategies in Vercel if traffic increases.

For now, these optimizations aren't necessary, but they represent the system's logical evolution if the site scales.

Let’s talk about costs

Another important aspect is that the infrastructure is kept deliberately lightweight. The Vercel frontend operates under a generous free tier, sufficient for personal projects, including bandwidth, serverless functions, and storage.

The backend, in this case Railway, has a low cost based on actual consumption. Since the CMS doesn't receive public traffic, its usage is minimal. Finally, the domain represents the only significant fixed cost.

Service Cost
Vercel $0
Railway ~20 MXN
Domain ~17 MXN

Overall, the system has a very low cost, especially considering what it enables: a site of my own, total content control, and a scalable architecture from day one.

Evolution and future improvements

Although the current architecture is sufficient, there are several logical improvements for the future:

  • Dedicated storage: Moving media to a dedicated system would improve image distribution and reduce the load on the CMS.
  • MDX adoption: This would allow integrating interactive components directly into the content, making articles more dynamic without losing the system's structure.
  • User sessions: As the project grows, introducing user sessions could enable features like comments, newsletters, or personalized content, moving the system toward a more interactive architecture.

Conclusion

The conclusion is simple: creating a website today is more accessible than it seems, both in cost and complexity, and it remains a powerful tool for building a digital presence.

If this gave you ideas or motivated you to start your own, then mission accomplished. If you have any questions or comments, don't hesitate to reach out via my social media.

Volver al inicio