[Opinion]Today's frontend is easy to be messed up and we need to organize it

Published: (February 1, 2026 at 01:25 AM EST)
9 min read
Source: Dev.to

Source: Dev.to

Disclaimer: This post is a mixture of ranting about the complexity of the contemporary frontend and my thoughts on how to solve it. I have been away from the frontend/React world for a while, so this post might contain some out‑of‑date ideas. Please let me know if you find such ideas while reading. Although I only discuss React/Next.js here, I think the argument could be extended to the entire frontend ecosystem, regardless of framework. Therefore I will use the words frontend and React interchangeably throughout the post.

React is dang complex

Lately, I was involved in a task to develop an application where the frontend is built with Next.js. Although I have done many tasks that required React and Next.js, those times I didn’t have to deal with the design part; I was merely passing data from the backend to React components for presentation. This was my first time having to seriously consider visual design—from layout down to the font color of a small piece of text inside a “—and, man, it was super difficult!

Why?
It wasn’t just the overwhelming amount of CSS documentation on MDN (though that helped). The harder part was that it is very easy to write messy React components. By “messy code” I mean components whose purpose or responsibility is hard to recognize, and whose behavior and state updates are difficult to track.

Before you blame me for a skill issue, think about the last React/Next.js code you encountered. If you can’t recall one, browse the advanced topics on the official React documentation homepage. For example, the excerpt below is taken from the page about managing input and state:

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return 
## That's right!
;
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      
## City quiz

      
        In which city is there a billboard that turns air into drinkable water?
      
      
        
        

        
          Submit
        
        {error !== null && (
          
{error.message}

        )}
      
    
  );
}

Even though this example is relatively simple, it already contains:

  • Three pieces of state
  • Nested HTML elements
  • Two event handlers
  • Conditional rendering

Why does something that looks so straightforward feel complex?

I believe there is an innate issue that is hard to overcome in React (and the frontend in general): a React component must represent information from a managed state in JSX, which is essentially a “nicer” version of HTML. It sounds natural, but it introduces a fundamental tension.

Frontend = State + Hierarchical Presentation

Any frontend technology—web or mobile—is essentially about managing the current state (whether on the client or the server) and rendering it in a hierarchical view. The hierarchical nature brings several challenges, such as:

  • Layout – How do sibling HTML nodes relate to each other? How many children should a “ contain? How do we handle responsive design?
  • State management – Should we fetch all data in a single place and distribute it to many components, or let each small component fetch its own data? How do we handle updates and re‑rendering? Should data fetching happen in a parent or in its children?

Because state management and layout are tightly coupled while the UI must remain hierarchical, complexity (or “messiness”) quickly escalates.

Consider the previous Form example. Suppose we want to replace the with a so users can choose an answer from a dynamic list fetched from the backend. A natural first step might be to add a useEffect inside the same Form component:

export default function Form() {
  // …
  const [countries, setCountries] = useState([]);

  useEffect(() => {
    async function fetchCountries() {
      try {
        const response = await fetch('GET_COUNTRY_LIST_API');
        const data = await response.json();
        setCountries(data.countries || []);
      } catch (error) {
        setError(error);
      }
    }

    fetchCountries();
  }, []);

  // …
}

Is this a good solution? Some of you might say yes; others may disagree. Should the countries list be fetched in the same Form component that renders and submits the data, or…? (The discussion continues…)

Revisiting Container‑Presentational Pattern

Dan Abramov’s famous “Presentational‑Container” pattern provides a useful insight for organizing this mess (for an easier introduction, I recommend reading this post on patterns.dev).

From my understanding, you can have the following two patterns of writing React components:

TypeDescription
Stateful (or non‑functional)Manages the application’s internal state. This can be pure client‑side state (e.g., the current text value in an input) or data fetched from a backend or third‑party API. In short, any component that uses useState, useEffect, or fetch is stateful.
Stateless (or purely functional)Purely functional – the data it reads is immutable and there are no side‑effects caused by useEffect. It is only responsible for visualising the given data.

Applying the pattern to the Form component

The Form component is obviously stateful, because it manages several states using useState. If we ever add useEffect here for fetching the list of candidate countries, then the component is also responsible for handling data fetched from the backend.

This separation of concerns is especially useful for maintenance:

  • To add any additional data submission, tweak the Form component.
  • If there is a problem submitting the country text, the bug must be inside this Form.

If we refactor the component according to the Presentational‑Container pattern, we separate the markup (the presentational part) from the state‑handling logic (the container part). The presentational component could look like this:

export const FormBox = ({
  title,
  description,
  answer,
  status,
  error,
  handleSubmit,
  handleTextareaChange,
}: Props) => {
  return (
    <>
      
## {title}

      
{description}

      
        
        

        
          Submit
        

        {error !== null && (
          
{error.message}

        )}
      
    
  );
};

Note: The container component would import FormBox and pass the appropriate props (state values and callbacks).

Hierarchical concerns

Even with this logical separation, the frontend hierarchy can still blur the lines between stateful and stateless components. There is no limit to nesting a stateful element inside a stateless one. Consider the following example:

export function FormLayout() {
  return (
    
      {/* some other components */}
      
    
  );
}

FormLayout itself does not contain any submission logic, yet you will likely inspect it while debugging because it conceptually groups the form. This shows that a more comprehensive mental model is needed for organizing frontend code.

Revisiting Atomic Design

Brad Frost’s Atomic Design offers another useful perspective for structuring a React project. While Frost defines five levels of components (atoms, molecules, organisms, templates, pages), my takeaway is that an entire frontend page can be thought of in two aspects:

AspectDescription
LayoutConcerns how visual components are placed on the screen (size, positioning, flexbox arrangement, etc.). This is primarily a CSS concern, but each component should be self‑contained so it does not unintentionally affect its siblings (e.g., via overflow or resizing).
Feature pageConcerns what the component is trying to convey to the user from a product standpoint. Each feature should follow the Single‑Responsibility Principle. A feature may consist of several sub‑features (e.g., a form page with text inputs, file uploads, etc.). Each sub‑feature handles its own UI and data state.

Naming considerations

The name FormLayout can be misleading because it may contain not only the form itself but also unrelated elements such as a navigation bar or an advertisement banner. In such cases a more descriptive name—e.g., QuizPageLayout—might be appropriate.

Putting it all together

We now have a mental model for separating concerns:

  1. Hierarchical feature structure – The entire project is organized as a tree of features.
  2. Page‑level layout – Each top‑level feature is assigned its own page by a layout component.
  3. Feature‑level state – Each feature fetches and updates its own data, keeping state isolated.

By combining the Container‑Presentational pattern with the principles of Atomic Design, we can achieve a clean, maintainable, and scalable frontend architecture.

Layout – Page Model

Let’s discuss the Layout‑Page model in more detail, in conjunction with the Container‑Presentational pattern we covered previously.

What is a Layout?

  • Layout corresponds to organisms, templates, and pages in Atomic Design.
  • It is responsible only for arranging several components on the entire screen: deciding the position, display, and size of each component.
  • It may contain visual helpers such as dividers, but those are rare.
  • Layout never deals with how to render each individual component (i.e., the Page), nor with its margin or padding properties.

What is a Page?

  • A Page represents a single feature in the product, following the Single‑Responsibility Principle.
  • It is responsible for fetching and updating the data relevant to the feature in the backend, and for managing UI state on the client side when necessary.
    • Stateless, purely functional pages that only visualize data passed to them are also valid.
  • A Page consists of two elements: its own layout and sub‑pages.
    • If a page contains only basic HTML elements and no other React components, it can be considered a leaf page.

Because each page can contain its own layout and sub‑pages, the structure is recursive: a tree of pages, each logically separated into layouts and sub‑pages.

Example Tree

QuizPage
├── @AdsBanner
│   ├── @Page
│   └── Layout
├── @QuizSubmitPage
│   ├── @Page   #  will be in this page
│   └── Layout
└── Layout

The tree mirrors the structure of the Next.js App Router, although Next.js does not enforce any particular design principles. The App Router’s file‑based routing fits naturally with the Layout‑Page model, and the model was partly inspired by it.

Mapping to Next.js File Routing

quiz
├── @adsbanner
│   ├── page.tsx
│   └── layout.tsx
├── submit
│   ├── page.tsx   #  will be in this page
│   └── layout.tsx
├── layout.tsx
└── page.tsx
  • The ads banner uses a parallel route (see the Next.js parallel routes documentation) so that the banner is not exposed as a standalone route.
  • Because it lives under quiz/ rather than quiz/submit/, the banner appears on all sub‑routes of quiz/, not just on the submission page.
  • Parallel routes are essential for the Layout‑Page model, as a product feature may contain multiple sub‑features.

Full Project Recursion

Project
├── Layout
├── Page0
│   ├── Layout
│   ├── Page00
│   │   ├── Layout
│   │   ├── Page000
│   │   …
│   ├── Page01
│   ├── Page02
│   …
├── Page1
├── Page2
├── Page3

Closing Thoughts

I may not be the original creator of this mental model; someone else might have already published a similar idea under a different name. After a long search for effective ways to organize messy front‑ends, this model emerged for me. I’d love to hear any comments or references you might have. Thank you!

Back to Blog

Related posts

Read more »

ReactJS ~React Server Components~

!Cover image for ReactJS ~React Server Components~https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev...

Happy New Year!

Introduction This is a submission for the New Year, New You Portfolio Challenge presented by Google AI. Hi, I'm Hyunwoo, a software developer based in Montreal...