PODS Architecture: Code Islands for Scalable Frontend Projects

Have you ever worked on a frontend project where finding a file felt like searching for a needle in a haystack? Or where a change in one component caused unexpected side effects in other parts of the application? If the answer is yes, PODS architecture might be the solution you've been looking for.


The Origin: Ember.js and Feature-Based Organization

The PODS concept has its roots in Ember.js, a framework that from its early days championed an organized and predictable project structure. Ember introduced the idea of grouping code by functionality rather than by technical type.

In the traditional structure of many frameworks, we often find something like this:

src/
├── components/
│   ├── patient-form.tsx
│   ├── patient-list.tsx
│   ├── invoice-form.tsx
│   └── invoice-list.tsx
├── services/
│   ├── patient.service.ts
│   └── invoice.service.ts
├── models/
│   ├── patient.model.ts
│   └── invoice.model.ts

The problem with this approach is that when you need to work on the "patients" functionality, you have to navigate between multiple folders. Additionally, it's easy for a change in patient.service.ts to affect components you didn't expect.

Ember proposed an alternative: group everything related to a functionality in the same place, creating what they called pods.


What is a POD?

A POD is a code module that contains everything it needs to function autonomously.

Think of it as a code island: it has its own components, its own API, its models, its mappers, its business logic... all encapsulated in a single folder. Communication with the outside world occurs only through cross-cutting functionalities (such as authentication, routes, or application themes).

An example of a pod structure:

src/
├── pods/
│   ├── patient/
│   │   ├── api/
│   │   │   ├── patient.api-model.ts
│   │   │   └── patient.api.ts
│   │   ├── components/
│   │   │   └── patient-card.astro
│   │   ├── patient.business.ts
│   │   ├── patient.mapper.ts
│   │   ├── patient.model.ts
│   │   ├── patient.pod.astro
│   │   └── index.ts
│   ├── invoice/
│   │   ├── api/
│   │   ├── components/
│   │   ├── invoice.business.ts
│   │   ├── invoice.mapper.ts
│   │   ├── invoice.model.ts
│   │   ├── invoice.pod.astro
│   │   └── index.ts

Advantages of PODS Architecture

1. Reduced Onboarding Time

When a new developer joins the team, they can focus on a specific pod without needing to understand the entire project. Everything they need is in one folder.

2. Lower Risk of Collisions

Each developer can work on their pod without stepping on others' work. Git conflicts are drastically reduced.

3. Change Isolation

A change in the "patients" pod shouldn't affect the "invoices" pod. If something breaks, you know exactly where to look.

4. Easier Migrations

If you need to migrate part of the application to another technology, you can do it pod by pod instead of rewriting everything.

5. ViewModels Tailored to Each View

Each pod defines its own ViewModels, specifically focused on what the UI needs. This avoids carrying unnecessary data or adapting generic models.

6. Simpler Testing

By separating code into small pieces with clear responsibilities (mappers, business, api), writing unit tests becomes more natural.


The Rules of the Game

For PODS architecture to work, certain golden rules must be respected:

Rule 1: A pod cannot import from another pod

This is the most important rule. If a pod needs something from another pod, it's a sign that:

  • That functionality should be in common (if it's generic)
  • Or in core (if it's cross-cutting to the application)

Exception: A "container pod" can import the main component of its child pods, but never their internals.

Rule 2: Duplication is better than coupling

Is the API model for "patients" very similar to "contacts"? It doesn't matter. Each pod defines its own model. It may look like duplicate code, but it's actually independence.

Mental note: If you see yourself repeating code, it's a sign that you might need to extract something to common or core, but not at the expense of breaking the pods' isolation.

Rule 3: Pages/scenes should be thin

A page should only:

  • Choose which layout to use
  • Handle URL parameters
  • Compose one or more pods

No business logic, no API calls, no data transformations, unless strictly necessary (for example in Astro when using getStaticPaths in SSG mode).

Rule 4: Cross-cutting goes to core, reusable goes to common

  • core: routes, authentication, theme, global providers
  • common: generic UI components, utilities, validations
  • common-app (optional): reusable components tied to the domain

File Structure Within a POD

Each file has a clear purpose:

File Responsibility
*.api-model.ts Types representing the API response (DTOs)
*.api.ts Functions for making HTTP calls
*.model.ts ViewModels adapted to the UI
*.mapper.ts Conversion between API models and ViewModels
*.business.ts Pure business logic (no framework dependencies)
*.pod.astro Main pod component
components/ Internal subcomponents of the pod

Applying PODS in Astro

In Astro projects, PODS architecture fits especially well with the framework's own islands architecture concept. Each pod acts as an independent island that can be rendered in isolation.

The typical structure would be:

src/
├── common/           # Generic UI, utilities
├── core/             # Routes, auth, theme, providers
├── layouts/          # Astro layouts
├── pages/            # Pages (pod composition)
├── pods/
│   ├── hero/
│   ├── contact-form/
│   ├── blog-list/
│   └── ...

An Astro page would be as simple as:

---
import MainLayout from '@/layouts/astro/main.layout.astro';
import HeroPod from '@/pods/hero/hero.pod.astro';
import BlogListPod from '@/pods/blog-list/blog-list.pod.astro';
import ContactFormPod from '@/pods/contact-form/contact-form.pod.astro';
---

<MainLayout title="Home">
  <HeroPod />
  <BlogListPod />
  <ContactFormPod />
</MainLayout>

When NOT to Use PODS?

PODS architecture shines in medium and large projects, but can be overkill for:

  • Very simple landing pages
  • Quick prototypes
  • Projects with 2-3 views without complexity

In these cases, a flatter structure may be sufficient. The key is not to over-engineer.


Conclusion

PODS architecture, inspired by Ember.js, offers a structured and scalable way to organize frontend projects. By treating each functionality as an independent island, we reduce coupling, facilitate teamwork, and make code more maintainable.

It's not a silver bullet, but if your project has grown to the point where finding a file has become an adventure, maybe it's time to give pods a try.


References