← Back to the blog

29 August 2026

·

9 min read

An MPA in 2026 Might Not Be Such a Bad Idea

Not all your projects necessarily need to be Single Page Applications.

Software Design Web Development Multi-Page Applications My Opinions Are Very Interesting
An MPA in 2026 Might Not Be Such a Bad Idea

A Quick Overview

It’s late summer 2026, the first version of React is 13 years old (2013), and almost all web solutions are now based on JavaScript frameworks and Single Page Application (SPA) architectures.

The good old Multi-Page Applications (MPAs) quickly became outdated in the face of the interactivity and fluidity promised by SPAs. I still vividly remember the slightly dizzying moment when, on my first AngularJS page, I saw the DOM update without a page reload—or when I could navigate from one view to another without ever leaving the application.

In my small circle of freelance web developers I work with, almost without exception, when a new from-scratch project comes in, the question doesn’t even arise: it’ll be a Node backend (Express or Nest in 99% of cases) and a frontend built with the holy trinity of SPAs (React, Vue, or Angular).

And Yet

And yet, while I genuinely believe that in many cases a clear separation between frontend and backend is entirely beneficial, for the projects I have to manage alone—with tight budgets and deadlines—I remain totally convinced that developing a Multi-Page Application with good old PHP/Symfony is often the most pragmatic solution.

The Majestic Monolith

Behind this concept is David Heinemeier Hansson, the creator of Ruby on Rails.

When he’s not discussing social or political issues, he’s absolutely fascinating from a technical and conceptual standpoint, with a very clear vision of what a well-designed web application should be.

He argues that for the majority of web projects, a well-structured monolith is often more effective than a fragmented architecture of microservices or SPAs. According to him, the complexity introduced by separating frontend and backend and multiplying services can quickly become a hindrance to productivity and maintainability.

In short, the Majestic Monolith advocates for an approach where everything is centralized—but in an organized and thoughtful way—allowing for greater simplicity and efficiency.

Basically, while the concept goes far beyond the simple question of whether to separate frontend and backend, it emphasizes the importance of coherence and not over-engineering an application unnecessarily. The goal is to be able to develop new features quickly without sacrificing code quality or the final output.

How Does This Relate to MPAs?

If you develop a SPA, for the sake of fluidity and interactivity, you’ll have to accept adding a non-negligible layer of complexity:

  • Managing synchronization between your frontend and backend.
    • You’ll need to fetch data in JSON format via numerous API calls.
    • Aggregate this data on the frontend and make it usable in your application.
    • Maintain a consistent state in your frontend application, often using libraries like Redux, Pinia, or NgRx for the most hardcore developers.
  • Handling errors and loading states on the frontend to ensure a smooth user experience, even if there are communication issues with the backend.
  • Hosting is also a bit more complex, as you’ll need to maintain both the frontend and backend, often on separate servers or services, which can add extra constraints in terms of deployment and resource management.

In short, all of this means more code to write, maintain, and test.

A Small Example

Let’s take a concrete example: imagine you need to create a simple authentication system for your web application, something basic, using a username/password combo and a client-side JWT.

Backend

For the backend, you’d need to:

  • Create a route for authentication (e.g., /login).
  • Verify the user’s credentials.
  • Generate a JWT if the credentials are correct.
  • Return the JWT to the client.

Here’s a terrible Node example:

// Route /login
async function login(req, res) {
    const user = await findUserByEmail(req.body.email);
    if (!user || !passwordVerify(req.body.password, user.password)) {
        return res.status(401).json({ error: 'Invalid credentials' });
    }
    const token = generateJWT(user);
    return res.json({ token });
}

Frontend

For the frontend, you’d need to:

  • Create a login form where the user can enter their email and password.
  • Send the credentials to the backend via an HTTP request (e.g., fetch or axios).
  • Receive the JWT in response and store it client-side (e.g., in localStorage or cookies).
  • Inject the JWT into the headers of subsequent HTTP requests to authenticate the user with the backend.

And if the JWT doesn’t contain user information, you’ll need to make an additional request to the backend to fetch the user details after authentication.

Back to the Backend

When the frontend makes a request to fetch user details after authentication, the backend must:

  • Verify the JWT sent in the request headers.
  • Extract the user’s identity from the JWT.
  • Retrieve the user’s details from the database.
  • Return the user’s details to the frontend.

Here’s another terrible Node example:

// Route /me to fetch user details
async function getUserDetails(req, res) {
    const token = req.headers['authorization']?.split(' ')[1];
    if (!token) {
        return res.status(401).json({ error: 'No token provided' });
    }
    const userId = verifyJWT(token);
    if (!userId) {
        return res.status(401).json({ error: 'Invalid token' });
    }
    const user = await findUserById(userId);
    return res.json({ user });
}

Back to the Frontend

You’ve retrieved the user’s information, so you can now parse it, store it in your application’s state, and update the UI accordingly.

None of this is fundamentally complicated, but it’s a typical example of how a frontend and backend interact to handle authentication and user data retrieval.

That’s a lot of code. Code that can break, that needs to be maintained and secured.

Trust the Boring Tech

And this is where I come to our good old MPAs.

For me, an MPA means relying on a backend framework capable of directly generating HTML templates. In this case, no separate API, no JWTs circulating between client and server, no state to synchronize…

Your authentication relies on classic HTTP session management protocols, like session cookies. Major backend frameworks have handled this for years—it’s a largely solved problem.

Once logged in, you’ll always have access to the user’s information directly from the server via a method like getUser(), which you can use anywhere in the application (including in your HTML templates).

The only thing you’ll need to do is create a nice HTML page with a classic login form, add a little CSRF protection (largely handled by modern backend frameworks), and the rest is taken care of automatically.

For me, this is the biggest strength of the Multi-Page Application: all layers of your application are contained in one place. You can pass information and state from your database across your entire application without having to manage complex synchronization mechanisms between frontend and backend.

Less code, fewer chances of crashes, and less complexity for you as a developer to manage.

As a Freelancer or Solo Developer

If you’re tackling a from-scratch project solo, you need to focus on creating value. Creating value means delivering robust and useful features to your users, rather than spending too much time managing unnecessary technical complexities.

The time you save not having to glue together your frontend and backend can be invested in the technical architecture of your application, in writing more robust code (DDD, TDD, Clean Code, etc.), and in continuously improving your product.

Yes, But the UX Is Still Less Appealing on an MPA

That’s true: by default, the user experience on a Multi-Page Application is less appealing than on a Single-Page Application. We’ll see that there are solutions to mitigate this, but first, I invite you to ask yourself a few questions about the real needs of your users and the priorities of your project.

If you’re building a B2B application that needs to address very specific professional use cases, is it really important for the entire UI to update in real time?

Most of the B2B applications I’ve developed have turned out to be, in reality, more or less interactive forms and sortable data lists. The majority of the complexity in these applications often lies in managing business rules and permissions, rather than the user interface itself.

Additionally, modern CSS often allows you to create visually attractive interfaces without bombarding the client with JavaScript. A large part of what makes a user interface pleasant is its design and how elements are arranged and animated, rather than complex dynamic interactions.

So, before diving into building a complex SPA, ask yourself if it truly adds value for your users, or if a well-designed MPA might suffice.

On top of that, there are minimalist JavaScript solutions that can make your good old Multi-Page Application feel like an SPA, but I’ll cover that in a future article.

For Our Vibe-Coding Friends

If you’re one of those people who brag about not having written a line of code in months—or even ever—know that the MPA approach can be interesting. Why? Because it has the advantage of consolidating your entire application context within a single monolith. This makes it easier for your agent to develop complete features: in a single coding session, it can unroll all the layers of the application without having to juggle between different contexts or frameworks.

The Downsides of MPAs

Of course, Multi-Page Applications aren’t the perfect solution for every project. Even if we set aside full page reloads, there are a few cases where this type of setup complicates things.

  • You have multiple frontends: Typically, a web app and a mobile app. If your server is configured to generate HTML instead of JSON, you’ll have trouble delivering your data to your mobile app. You can obviously create specific API endpoints, but this requires extra software design effort to avoid duplicating business logic. Typically, you’d need to separate your business logic into distinct Use Cases, which could be reused by different controllers depending on the target frontend. It’s doable, but it requires a certain level of discipline and a well-thought-out architecture.

  • Your application is highly interactive: If your application requires a lot of real-time interactions, like instant updates or chat-like features, an MPA can become cumbersome and less responsive. In this case, a SPA or a hybrid approach might offer a better user experience.

Lessons Learned and Conclusions

On my end, since creating Shifumi, I’ve developed three business applications from scratch, and for the reasons mentioned above, I chose the Multi-Page approach for all of them.

Whether for the initial development phase, maintenance, or feature evolution, it’s a choice I do not regret at all.

During development, I was able to iterate on features more quickly while devoting more time to architecture and code quality.

For maintenance, debugging is faster and a little simpler. The stack trace is easier to follow in a large monolith.

Finally, for feature evolution, it’s easier to add new features without worrying about synchronization between multiple frontends or the complexity of a distributed architecture.

For a freelancer like me, the velocity I gain from this approach is invaluable. My clients often come back to me for small, occasional updates on these applications, and I can intervene quickly, even when I’m busy with other projects in parallel. This allows me to maintain a high level of client satisfaction while optimizing my time and resources.

In conclusion, even in 2026, I still consider the Multi-Page approach—and more broadly, the good old monolith—as a viable and effective solution for many web projects, especially when I’m the main developer on those projects.