Brand Logo
AboutPortfolioBlog
Let's Talk

A boutique software studio crafting digital experiences with human understanding and technical excellence.

Navigation

  • About
  • Portfolio

Resources

  • Blog

© 2026 OSSO Studio. Crafted with care.

Back to Blog
blog-1-thumbnail
architecture

De una landing page a una arquitectura completa

A visual overview of this website’s architecture: Next.js, Payload CMS, PostgreSQL, Cloudflare R2, Docker, Dokploy, and GitHub, and how they work together to serve pages and publish content.

Oscar Casado
October 27, 2026
9 min read

Table of contents

  • The frontend: the part visitors see
  • Payload CMS: writing content without editing code
  • GitHub, Docker, and Dokploy: from code changes to deployment
  • The final piece: Cloudflare

First of all, I’d like to explain why this website exists, especially since this will be the first of many articles about software development.

About five years ago, during the final stage of my Computer Engineering degree, I remember developing complex distributed systems, designing massive databases, and struggling to understand concepts such as algorithmic complexity, how networks work, and many other things that are part of most developers’ day-to-day work.

However, as useful as all that was, I still hadn’t figured out something fundamental by the end of the day:

  1. What steps do you need to follow to deploy a complex solution* to the internet
  2. How much does it cost to deploy a solution like this?
  3. And, most importantly, how do you do it properly?

*By a complex solution, I mean applications based on distributed systems in which several components, not necessarily connected to each other initially, need to interact in a synchronized way.

To answer these questions, I decided to build a website that could serve both as a case study and as a portfolio. And that’s how this website came to be. Well, not exactly this one, but an earlier version built as a Vue SPA, where most of the complexity came from developing the animations rather than from the architecture itself.

What happened next? I bought a domain, created a Netlify account, connected it to my GitHub repository, configured the DNS records, and that was it.

architecture-diagram-naive

It had been straightforward: read a handful of documentation pages, watch a few YouTube videos, spend a few dozen hours building the website, and call it a day.

I didn’t have any problems with it for the next four years.

Until six months ago.

— "I’d like to share my experiences with other developers."
— Should I write LinkedIn posts?
— "Hell no!"

The obvious option, then, was to write the articles on the website I already had. What could possibly go wrong?

Of course, the requirements had changed completely.

deployed-osso-first-version

After evaluating how well my old website could scale, I realized it was missing several fundamental elements that should have been in place long before development even began.

  1. Design and components:
    There was no defined design system or clear strategy for reusing components and managing themes. Although I was using Chakra UI, many components had been designed specifically for this application.
  2. Coupling:
    The solution was tightly coupled to the needs of that particular website. Reusing components, configurations, or patterns in other projects meant adapting them almost from scratch.
  3. Testing:
    There were no automated tests or tools for checking test coverage, health checks, code smells, and so on.
  4. Image management:
    All images were stored directly in the repository, although they had been properly optimized.
  5. Observability:
    I didn’t have a sufficiently comprehensive metrics system to understand how the application was behaving.
  6. Documentation:
    There was no documentation explaining the decisions made during development, such as the choice of external libraries, providers, ...

In short, the old website had more than just technical scalability issues. It was also tightly coupled to its implementation, difficult to reuse, and had accumulated a lot of knowledge that existed only in my head.

At that point, I realized what I needed: not simply a bigger application, but a foundation I could continue building on.

I’d be lying if I said I knew exactly how far this project would go, but the main goals were clear:

  1. Articles had to be created without writing code, through a decoupled application. This also meant having a database available in the cloud.
  2. The entire code deployment process had to be automated.
  3. The system needed a scalable architecture.
  4. I wanted full control over the entire process. No Platform as a Service (PaaS) or Backend as a Service (BaaS) solutions with subscriptions and hidden processes.

One thing to keep in mind is that, unlike corporate development, publishing personal projects comes with another consideration: the fewer subscriptions and services you have to pay for, the better. Every platform you integrate with imposes its own opinions on YOUR solution. Based on my experience, I recommend looking for open-source alternatives that help you reduce costs and retain control over the entire CI/CD process. You’ll also gain much more hands-on experience with DevOps. It’s a win-win.


So, what architecture did I go with? Here it is.

flow-chart-web

I’m not going to go into detail about why I chose these particular technologies or which other external services I use. But, as you can see, the system consists of:

  • A monorepo containing two separate applications:
    • The website (built with Next.js), which acts as the client and presentation layer for the content.
    • A CMS (built with PayloadCMS), which allows me to create and manage content more efficiently.
    • An internal documentation site built with Nextra.
  • A bucket for storing images and setting up caching through Cloudflare R2.
  • A relational database that stores the remaining content, references to the bucket, and the migration history.

The frontend: the part visitors see

Each blog post has a URL containing a language code and a slug. I designed it this way because, as we’ll see in future articles, using IDs or alphanumeric strings in URLs can hurt the SEO of publicly accessible pages. The URI structure is therefore as follows:

  • /en/blog/article-slug
  • /es/blog/slug-articulo

This way, the page knows which version of the content to request. When someone opens an article, the client fetches the content from the CMS API and renders the article page. Rich content is displayed using interface components for headings, paragraphs, links, lists, blockquotes, images, and code blocks.

ossobrand-workflow

The same applies to the Portfolio and About pages. Instead of storing each piece of text in i18n files that would need to be version-controlled alongside the rest of the code and make it harder to manage new content, this information is managed through the CMS, with separate entries for each language.


Payload CMS: writing content without editing code

Payload CMS is the content management system used by this application. From its admin panel, you can create and edit blog posts, manage images, and organize other site data, such as projects, categories, skills, and experience.

The text editor uses Lexical. Content isn’t stored as a manually written HTML page, but as a data structure describing its different elements: paragraphs, headings, links, lists, blockquotes, images, and code blocks. The frontend then interprets that structure and turns it into visual elements.

But Payload does more than provide an interface for editing content. It also defines the application’s data model, handles persistence, and exposes the API consumed by the frontend.

This is where PostgreSQL comes in.

The different collections we define in Payload — blog posts, projects, categories, skills, professional experience, and so on — are ultimately represented as persisted data in our relational database. Payload manages this layer and exposes an interface that allows the web application to query that data without needing to know exactly how it’s stored.

At the time of writing, the database’s entity-relationship diagram looks something like this:

ossobrand-er-diagram

The diagram helps reveal something that may be less obvious from Payload’s admin panel: behind the collections and relationships we manage through the CMS is a data model that defines how the different pieces of content relate to one another.

This separation is important from an architectural perspective. The website doesn’t need to know PostgreSQL’s internal schema; it communicates with Payload through its API, which acts as the boundary between the presentation layer and the data layer.

In return, this boundary makes the API a contract between the two applications: changes to the data, relationships, or API responses can affect the public website, even when each service is built independently.

In other words, Payload isn’t simply "where I write my articles." It’s the component that connects content management, the data model, and the API consumed by the frontend.


GitHub, Docker, and Dokploy: from code changes to deployment

The deployment process starts with the GitHub monorepo and relies on a separate Docker image for each application. The Dockerfiles define how to build the frontend and the CMS, while Dokploy coordinates the builds and runs each image as a service. This means deployment doesn’t depend on manually setting up each server: the code and the process used to package it move forward together.

This workflow makes it possible to take a change from the repository to production in a matter of minutes. Before that happens, staging environments provide a validation stage similar to UAT: the deployed application is tested under conditions similar to the real environment before the change is exposed to users.

GitHub stores the code; Docker makes the build artifact reproducible; Dokploy automates the build and execution process. The result is a short delivery cycle, with a validation stage before production and clearly defined boundaries between services.

code-to-live-diagram

The final piece: Cloudflare

Finally, Cloudflare is a central part of the infrastructure. It manages DNS and, with the proxy enabled, public domains resolve to Cloudflare’s network rather than directly to the VPS IP address. Cloudflare receives incoming requests and forwards them to the origin server, which sits behind that layer.

This setup makes it possible to apply security rules and mitigate denial-of-service attacks before traffic reaches the VPS. It also allows responses and resources covered by the configured rules to be cached, reducing requests to the origin server and improving delivery performance. The extent of caching depends on those rules: not all traffic is necessarily cached.

Cloudflare also provides R2 for media files. Payload uploads the files to the bucket and stores their metadata and relationships in PostgreSQL, while the browser requests the files through their public URLs. This means pages are delivered through the frontend, while media files are served from R2, with Cloudflare forming part of the infrastructure responsible for receiving and delivering traffic.


To wrap things up, you don’t master architecture through theory alone. You learn by making decisions, putting them to the test, and understanding their consequences. Every project is an opportunity to expand what we think is possible and turn uncertainty into experience. As Linus Torvalds said:

"Talk is cheap. Show me the code."

Ultimately, pick a challenge, build something, and learn along the way.

Last updated: October 27, 2026

Read More Articles