DEV Community

Cover image for How I approach and structure Enterprise frontend applications after 4 years of using Next.js
Joseph Mukorivo
Joseph Mukorivo

Posted on • Edited on • Originally published at josemukorivo.com

How I approach and structure Enterprise frontend applications after 4 years of using Next.js

Introduction

In the fast-paced world of frontend development, staying ahead of the curve is essential for building successful enterprise applications. After four years of using Next.js and a powerful toolkit that includes Tailwind CSS, TypeScript, TurboRepo, ESLint, React Query, and more, I have gained valuable insights and best practices to share with fellow developers. In this blog post, we will explore how to architect and structure frontend applications for large-scale enterprises, maximizing performance, maintainability, and scalability.

NB This article expresses personal viewpoints, and the methods I advocate may not be suitable for your specific situations.

Guiding Principles of Effective Enterprise Frontend Architecture

When it comes to architecting frontend solutions for enterprise-scale applications, having a well-defined set of principles can be the North Star that keeps your development efforts on course. In this section, I'll share the guiding principles that have emerged from my experience with Next.js in enterprise environments.

Modularity and Componentization

Principle: Divide and Conquer

In the sprawling landscape of enterprise applications, code can quickly become an unruly beast. Embrace modularity and componentization to break down your frontend into manageable pieces. Think of components as Lego blocks, each serving a specific purpose. This not only enhances code reusability but also simplifies maintenance and collaboration within your development team. Consider not only the segmentation of your application into smaller components, but also the possibility of breaking it down into smaller standalone applications. This is an area where tools like Turbo repo excel.

Separation of Concerns (SoC)

Principle: Keep Your Codebase Neat

To maintain code sanity, adhere to the Separation of Concerns (SoC) principle. Ensure that your components focus on their respective responsibilities, whether it's rendering UI, handling business logic, or managing state. This segregation not only makes code easier to understand but also facilitates testing and debugging.

Scalability by Design

Principle: Plan for Growth

Enterprise applications aren't static; they evolve. Design your frontend architecture with scalability in mind. This means selecting patterns and tools that can accommodate increased traffic, data volume, and feature complexity. Next.js's scalability-friendly design can be a valuable ally in this endeavor.

Maintainability and Code Quality

Principle: Craft with Care

Code is your product's foundation. Prioritize maintainability and code quality from day one. Enforce coding standards, conduct code reviews, and invest in automated testing. A well-maintained codebase is not only easier to work with but also less prone to bugs and regressions. At work I recently developed a component library and a basic style guide to enforce standards on our frontend applications. Don't mind the docs they are not yet doneπŸ˜‚.

Accessibility by Default

Principle: Inclusive from the Start

Accessibility is a non-negotiable aspect of modern web development. Make it a default practice from the beginning. Ensure your application is usable by all, regardless of disabilities. Leverage Next.js's support for accessibility standards and tools to create inclusive user experiences. I use tools like Radix UI for some components that require accessibility like the tabs, dropdowns, etc.

Performance-Oriented Development

Principle: Speed Matters

Enterprise users expect snappy experiences. Prioritize performance at every turn. Optimize assets, minimize unnecessary requests, and leverage Next.js's performance features like automatic code splitting, streaming with suspense and image optimization. A fast application not only pleases users but also positively impacts SEO.

Security First

Principle: Guard Your Castle

Security should be woven into the fabric of your frontend architecture. Protect against common vulnerabilities like Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF). Stay vigilant with security updates and best practices, and consider Next.js's built-in security features as an extra layer of defense.

Internationalization (i18n) and Localization (l10n)

Principle: Think Globally

In our interconnected world, thinking globally is essential. Implement internationalization (i18n) and localization (l10n) from the outset to cater to a diverse user base. Next.js provides excellent support for these features, making it easier to create multilingual applications.

These guiding principles form the bedrock of an effective enterprise frontend architecture when working with Next.js. They act as a compass, ensuring that your development efforts align with the demands of large-scale applications, making them robust, maintainable, and user-friendly. In the following sections, we'll delve deeper into how these principles can be translated into actionable strategies and best practices.

Folder and file structure

In React, organizing your project with a well-thought-out folder structure is crucial for maintainability and scalability. A common approach is to arrange files based on their functionality and purpose. Here's the sample folder structure I typically use for my applications:

β”œβ”€ src/
β”‚ β”œβ”€ components/
β”‚ β”‚ β”œβ”€ ui/
β”‚ β”‚ β”‚ β”œβ”€ Button/
β”‚ β”‚ β”‚ β”œβ”€ Input/
β”‚ β”‚ β”‚ β”œβ”€ ...
β”‚ β”‚ β”‚ └─ index.tsx
β”‚ β”‚ β”œβ”€ shared/
β”‚ β”‚ β”‚ β”œβ”€ Navbar/
β”‚ β”‚ └─ charts/
β”‚ β”‚ β”‚ β”œβ”€ Bar/
β”‚ β”œβ”€ modules/
β”‚ β”‚ β”œβ”€ HomePage/
β”‚ β”‚ β”œβ”€ ProductAddPage/
β”‚ β”‚ β”œβ”€ ProductPage/
β”‚ β”‚ β”œβ”€ ProductsPage/
β”‚ β”‚ β”‚ β”œβ”€ api/
β”‚ β”‚ β”‚ β”‚ └─ useGetProducts/
β”‚ β”‚ β”‚ β”œβ”€ components/
β”‚ β”‚ β”‚ β”‚ β”œβ”€ ProductItem/
β”‚ β”‚ β”‚ β”‚ β”œβ”€ ProductsStatistics/
β”‚ β”‚ β”‚ β”‚ └─ ...
β”‚ β”‚ β”‚ β”œβ”€ utils/
β”‚ β”‚ β”‚ β”‚ └─ filterProductsByType/
β”‚ β”‚ β”‚ └─ index.tsx
β”‚ β”‚ β”œβ”€ hooks/
β”‚ β”‚ β”œβ”€ consts/
β”‚ β”‚ └─ types/
β”‚ β”‚ └─ lib/
| | └─ styles/
β”‚ β”‚ β”‚ β”œβ”€ global.css
β”‚ β”‚ └─ ...
β”‚ β”œβ”€ public/
β”‚ β”‚ β”œβ”€ ...
β”‚ β”‚ └─ index.tsx
β”‚ β”œβ”€ eslintrc.js
β”‚ β”œβ”€ package.json
β”‚ └─ tsconfig.json
└─ ...
Enter fullscreen mode Exit fullscreen mode
  • src/components: This directory contains your UI components. It's further subdivided into ui for generic UI components and shared for components that might be reused across different parts of your application.

  • src/modules: This directory houses your application's different modules or pages. Each module might have its own folder, containing subdirectories for API calls, components and utility functions.

  • src/pages: If you are using Next.js this folder should only be used as the entry point to your application. No business logic should reside here. Components in the pages folder should only render pages from the modules folder.

  • src/modules/ProductsPage: This module is related to products, and it contains subdirectories for API calls, components (like ProductItem and ProductsStatistics), and utility functions (filterProductsByType).

  • src/lib: This folder may contain utility functions that can be converted later into packages that are used across multiple applications. It's different from src/utils which may contain utility functions that do not make sense to convert into packages later.

  • src/styles: This directory holds global styles (global.css) and possibly other style-related files.

  • src/public: This folder contains static assets that don't go through the build process. It might include images, fonts, and the index.html file.

  • src/consts, src/types: These directories likely contain constants and TypeScript type definitions respectively.

  • src/hooks: This directory may house custom hooks that are used throughout your application.

  • eslintrc.js: This is a configuration file for ESLint, a popular JavaScript linting tool. It's used to enforce coding conventions and catch potential errors in your code.

The tsconfig file is configured such that if you for example want to import a Button component you can do it like so import { Button } from '@/components/ui'. Below is a snippet of how to configure that from tsconfig.json.

{
  ...
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript coding conventions

The conventions I follow are inspired by this guide here. I highly recommend you read it and the code snippets below are coming from that guide.

All types must be defined with type alias

// ❌ Avoid interface definitions unless you need to extend or implement them
interface UserRole = 'admin' | 'guest'; // invalid - interface can't define (commonly used) type unions

interface UserInfo {
  name: string;
  role: 'admin' | 'guest';
}

// βœ… Use type definition
type UserRole = 'admin' | 'guest';

type UserInfo = {
  name: string;
  role: UserRole;
};
Enter fullscreen mode Exit fullscreen mode

Avoid using multiple arguments

// ❌ Avoid having multiple arguments
transformUserInput('client', false, 60, 120, null, true, 2000);

// βœ… Use options object as argument
transformUserInput({
  method: 'client',
  isValidated: false,
  minLines: 60,
  maxLines: 120,
  defaultInput: null,
  shouldLog: true,
  timeout: 2000,
});
Enter fullscreen mode Exit fullscreen mode

Naming Conventions

Although determining the optimal name can be challenging, try to enhance code readability and maintain consistency for future developers by adhering to established conventions:

Variables

  • Locals Camel case products, productsFiltered
  • Booleans Prefixed with is, has etc. isDisabled, hasProduct
  • Constants Capitalized PRODUCT_ID
  • Object constants

Singular, capitalized with const assertion and optionally satisfies type (if there is one).

  const ORDER_STATUS = {
    pending: 'pending',
    fulfilled: 'fulfilled',
    error: 'error',
  } as const satisfies OrderStatus;
Enter fullscreen mode Exit fullscreen mode

Functions

Camel case

filterProductsByType, formatCurrency

Generics

A name starts with the capital letter T TRequest, TFooBar (similar to .Net internal implementation).

Avoid (popular convention) naming generics with one character T, K etc., the more variables we introduce, the easier it is to mistake them.

// ❌ Avoid naming generics with one character
const createPair = <T, K extends string>(first: T, second: K): [T, K] => {
  return [first, second];
};
const pair = createPair(1, 'a');

// βœ… Name starts with the capital letter T
const createPair = <TFirst, TSecond extends string>(
  first: TFirst,
  second: TSecond
): [TFirst, TSecond] => {
  return [first, second];
};
const pair = createPair(1, 'a');
Enter fullscreen mode Exit fullscreen mode

Packages and tools.

In application development, it's common practice to leverage third-party tools to avoid unnecessary duplication of work. Here are some of the packages I utilize when building scalable applications.

React Query/Tanstack Query

React Query is highly beneficial in managing data fetching and synchronization in complex enterprise applications. It provides a unified approach to fetching data from APIs, caching, and handling mutations. In enterprise settings, applications often need to interact with multiple APIs and services. React Query can streamline this process by centralizing data management and reducing boilerplate code.

React Context

React Context is instrumental in managing global state across various components without the need for prop drilling. This is particularly valuable in enterprise applications where shared state, such as user authentication or preferences, needs to be accessible throughout the application.

I generally reserve the use of React Context or other state management tools as a last resort. It's advisable to minimize reliance on global state. Instead, aim to keep your state closer to where it's specifically required.

Cypress

Cypress is an excellent tool for end-to-end (E2E) testing. In enterprise applications, ensuring that critical workflows and features function correctly across different screens and components is paramount. Cypress is by far my favourite tool. Whenever my tests pass it gives me confidence that the code I introduced did not break the application. As enterprise applications evolve, it's crucial to conduct regression testing to catch any unintended side effects of new code changes. Cypress facilitates this by automating the testing process.

React Testing Library:

React Testing Library is essential for unit and integration testing of React components. In enterprise applications, verifying that individual components work as expected is crucial for a robust application. React Testing Library allows for thorough testing of each component in isolation, as well as in conjunction with others.

NextAuth.js:

NextAuth.js simplifies the implementation of authentication and authorization in Next.js applications. In enterprise settings, secure user management is non-negotiable. Enterprises often employ Single Sign-On (SSO) solutions to streamline user authentication across multiple applications. NextAuth.js supports various SSO providers, making it an excellent fit for enterprise authentication needs. NextAuth.js also offers the flexibility to implement custom authentication flows.

I have a blog here that shows you how to customise default User model in NextAuth.js with TypeScript using module augmentation.

Turbo Repo

This is also my favourite tool. Turbo Repo is a valuable tool for managing monorepos. In large enterprise applications, codebases can be extensive, with various modules, services, and shared code. Turbo Repo aids in organizing, versioning, and deploying these codebases efficiently. In enterprise settings, code sharing across different teams and projects is common. Turbo Repo enables effective code sharing, allowing teams to collaborate on shared libraries and components.

Storybook

Storybook allows developers to isolate UI components and showcase them in a controlled environment. This makes it easy to demonstrate how individual components look and behave without the need to navigate through the entire application. In large enterprise applications, different developers or teams may be responsible for different parts of the UI. Storybook provides a centralized platform for showcasing and discussing UI components, fostering efficient collaboration and ensuring a consistent design language. Here is a sample component library I developed and documented using storybook.(It's still a work in progress btw)

In an enterprise context, these tools collectively provide a comprehensive toolkit for building, testing, and maintaining large-scale applications, addressing critical aspects like data management, state handling, testing, authentication, and code organization.

Coding style for reusable components

When I develop reusable components like inputs, dialogs, etc I try to follow some best practices.

Let's try some best practices for developing a Button component together and you will see that its more than just the visual design.

Component Reusability

Ensure that your button component is designed to be reusable across different parts of your application. It should be flexible enough to accommodate various use cases.

Props for Customization

Provide props for common customization options like size, color, variant (e.g., primary, secondary), and disabled state. This allows developers to easily adapt the button to fit different UI contexts.

Accessibility Considerations

Implement proper accessibility features such as aria-label, aria-disabled, and focus management. This ensures that users of assistive technologies can interact with the button effectively.

Semantic HTML

Use semantic HTML elements (e.g., ) for your button component. This enhances accessibility and SEO, and ensures proper behavior across different devices.

Mimic the native button element

All these best practices we are following forces us to write predictable code. If you develop a custom button component, make it work and behave like a button. You will see from the example component we will write together that I try to include all props a button can take by extending the native button element.

Error Handling

If the button can potentially lead to an error state (e.g., submitting a form), provide a way to handle and communicate these errors to the user.

Testing

Write unit tests to verify that the button component behaves as expected in different scenarios. Test cases should cover various props and event handlers.

Documentation

Document the usage of the button component, including available props, event handlers, and any specific use cases. Provide examples and code snippets to guide developers. This is where storybook shines.

Cross-Browser Compatibility:

Test the button component in different browsers to ensure consistent behavior and appearance.

Versioning and Changelog

If the button component is part of a shared library, implement versioning and maintain a changelog to keep developers informed of updates and changes.

Coding

For my components I usually have files like these. Button.tsx, Button.stories.tsx, Docs.mdx, Button.test.ts. If you are using CSS you may have something like Button.module.css.

components/ui/Button.tsx
This is the main component and the cn function merges the classes and handles conflicts. It's a wrapper around the tw-merge library.

import React from 'react';
import {
  forwardRef,
  type ButtonHTMLAttributes,
  type JSXElementConstructor,
  type ReactElement,
} from 'react';
import { AiOutlineLoading3Quarters } from 'react-icons/ai';
import type { VariantProps } from 'cva';
import { cva } from 'cva';
import Link from 'next/link';
import { cn } from '@/lib';

const button = cva(
  'flex w-max items-center border-[1.5px] gap-2 transition duration-200 ease-linear focus:outline-0 focus:ring ring-offset-1 dark:ring-offset-blue-dark',
  {
    variants: {
      variant: {
        outline: '...',
        solid: '...',
        naked: '...',
      },
      rounded: {
        none: 'rounded-none',
        sm: 'rounded',
        md: 'rounded-lg',
        lg: 'rounded-xl',
        full: 'rounded-full',
      },
      color: {
        primary: '...',
        danger: '...',
        info: '...',
        warning: '...',
        light: '...',
        secondary: '...',
      },
      size: {
        xs: '...',
        sm: '...',
        md: '...',
        lg: '...',
      },
      disabled: {
        true: '...',
      },
      active: {
        true: '...',
      },
      loading: {
        true: '...',
      },
      fullWidth: {
        true: '...',
      },
      align: {
        center: '...',
        left: '...',
        right: '...',
        between: '...',
      },
    },
    compoundVariants: [
      {
        variant: 'solid',
        color: ['secondary', 'warning', 'danger', 'info'],
        className: '...',
      },
      {
        variant: 'solid',
        color: 'primary',
        className: '...',
      },
      {
        variant: 'outline',
        color: ['primary', 'secondary', 'warning', 'danger', 'info'],
        className: '...',
      },
      {
        variant: 'outline',
        color: 'light',
        className:
          '...',
      },
      {
        variant: 'naked',
        color: ['primary', 'secondary', 'warning', 'danger', 'info'],
        className:
          '...',
      },
      {
        disabled: true,
        variant: ['solid', 'outline', 'naked'],
        color: ['primary', 'secondary', 'warning', 'danger', 'info', 'light'],
        className: '...',
      },
      {
        variant: 'outline',
        color: ['primary', 'secondary', 'warning', 'danger', 'info', 'light'],
        className: '...',
      },
      {
        variant: 'naked',
        color: 'primary',
        className: '...',
      },
    ],
    defaultVariants: {
      size: 'md',
      variant: 'solid',
      color: 'primary',
      rounded: 'lg',
      align: 'center',
    },
  }
);

interface BaseProps
  extends Omit<
      ButtonHTMLAttributes<HTMLButtonElement>,
      'color' | 'disabled' | 'active'
    >,
    VariantProps<typeof button> {
  href?: string;
  loadingText?: string;
  target?: '_blank' | '_self' | '_parent' | '_top';
  as?: 'button' | 'a' | JSXElementConstructor<any>;
}

export type ButtonProps = BaseProps &
  (
    | {
        rightIcon?: ReactElement;
        leftIcon?: never;
      }
    | {
        rightIcon?: never;
        leftIcon?: ReactElement;
      }
  );

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => {
    const {
      as: Tag = 'button',
      variant,
      color,
      rounded,
      size,
      target = '_self',
      loading,
      fullWidth,
      align,
      loadingText,
      href,
      active,
      rightIcon,
      leftIcon,
      className,
      disabled,
      children,
      ...rest
    } = props;

    const classes = cn(
      button({
        variant,
        color,
        size,
        disabled,
        loading,
        active,
        rounded,
        fullWidth,
        align,
      }),
      className
    );

    return (
      <>
        {href ? (
          <Link className={classes} href={href} target={target}>
            {leftIcon}
            {children}
            {rightIcon}
          </Link>
        ) : (
          <Tag className={classes} disabled={disabled} ref={ref} {...rest}>
            {loading ? (
              <>
                <AiOutlineLoading3Quarters className='animate-spin' />
                {loadingText || 'Loading...'}
              </>
            ) : (
              <>
                {leftIcon}
                {children}
                {rightIcon}
              </>
            )}
          </Tag>
        )}
      </>
    );
  }
);

Button.displayName = 'Button';

Enter fullscreen mode Exit fullscreen mode

components/ui/Button.stories.tsx

This file has the button stories for storybook.

import { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { FaRegSmileWink, FaThumbsUp, FaYinYang } from 'react-icons/fa';
import { FiArrowUpRight } from 'react-icons/fi';
import { Button } from './Button';

export default {
  title: 'Components/Button',
  component: Button,
  parameters: {},
  args: {
    children: 'Click me!',
  },
  argTypes: {
    children: {
      description: 'This is the text of the button, can be a node.',
      control: { type: 'text' },
    },
    color: {
      options: ['primary', 'danger', 'info', 'warning', 'secondary', 'light'],
      control: { type: 'select' },
      description: 'This controls the color scheme of the button',
      table: {
        defaultValue: { summary: 'primary' },
      },
    },
    variant: {
      options: ['solid', 'outline', 'naked'],
      control: { type: 'select' },
      description: 'This controls the variant of the button',
      table: {
        defaultValue: { summary: 'solid' },
      },
    },
    size: {
      options: ['sm', 'md', 'lg'],
      control: { type: 'radio' },
      description: 'This controls the size of the button',
      table: {
        defaultValue: { summary: 'md' },
      },
    },
    loading: {
      control: { type: 'boolean' },
      description: 'This controls the loading state of the button',
      table: {
        defaultValue: { summary: false },
      },
    },
    href: {
      control: { type: 'text' },
      description:
        'If this is set, the button will be rendered as an anchor tag.',
    },
    className: {
      control: { type: 'text' },
      description: 'Classes to be applied to the button',
    },
    disabled: {
      control: { type: 'boolean' },
      description: 'If true, the button will be disabled',
      table: {
        defaultValue: { summary: false },
      },
    },
    rightIcon: {
      options: ['Smile', 'ThumbsUp', 'YinYang'],
      mapping: {
        Smile: <FaRegSmileWink />,
        ThumbsUp: <FaThumbsUp />,
        YinYang: <FaYinYang />,
      },
      description:
        'If set, the icon will be rendered on the right side of the button',
    },
    leftIcon: {
      options: ['Smile', 'ThumbsUp', 'YinYang'],
      mapping: {
        Smile: <FaRegSmileWink />,
        ThumbsUp: <FaThumbsUp />,
        YinYang: <FaYinYang />,
      },
      description:
        'If set, the icon will be rendered on the left side of the button',
    },
    loadingText: {
      control: { type: 'text' },
      description:
        'If set, the text will be rendered while the button is in the loading state',
    },
    target: {
      control: { type: 'text' },
      description:
        'If set, the target will be rendered as an attribute on the anchor tag',
      table: {
        defaultValue: { summary: '_self' },
      },
    },
    as: {
      options: ['button', 'a'],
      control: { type: 'select' },
      description:
        'If set, the button will be rendered as the specified element',
      table: {
        defaultValue: { summary: 'button' },
      },
    },
  },
} as Meta<typeof Button>;

type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {},
};

export const Secondary: Story = {
  args: {
    color: 'secondary',
  },
};

export const Danger: Story = {
  args: {
    color: 'danger',
  },
};

export const Warning: Story = {
  args: {
    color: 'warning',
  },
};

export const Light: Story = {
  args: {
    color: 'light',
  },
};

export const Info: Story = {
  args: {
    color: 'info',
  },
};

export const Custom: Story = {
  args: {
    className: 'bg-[yellow] text-[black] border-[orange]',
    style: { borderRadius: '3.5rem' },
  },
};

export const WithRightIcon: Story = {
  args: {
    rightIcon: <FiArrowUpRight className='h-5 w-auto' />,
  },
};

export const WithLeftIcon: Story = {
  args: {
    leftIcon: <FiArrowUpRight className='h-5 w-auto' />,
  },
};

export const Disabled: Story = {
  args: {
    disabled: true,
  },
};

export const OutlineVariant: Story = {
  args: {
    variant: 'outline',
    color: 'danger',
  },
};

export const NakedVariant: Story = {
  args: {
    variant: 'naked',
    color: 'danger',
  },
};

export const Loading: Story = {
  args: {
    loading: true,
  },
};

export const CustomLoadingText: Story = {
  args: {
    loading: true,
    loadingText: 'Processing...',
  },
};

export const AsLink: Story = {
  args: {
    href: 'https://fin.africa',
    children: 'Visit fin website',
    rightIcon: <FiArrowUpRight className='h-5 w-auto' />,
  },
};

export const FullWidth: Story = {
  args: {
    fullWidth: true,
    children: 'Visit fin website',
    rightIcon: <FiArrowUpRight className='h-5 w-auto' />,
  },
};

Enter fullscreen mode Exit fullscreen mode

components/ui/Docs.mdx

The stories file is OK for documenting how the component works but a markdown file can have more extensive documentation.

The conventions I used to develop the Button component are the same conventions I try to follow for all my components.

Key takeaways

  • Have a design system of some sort, weather its an open source solution or you spin up your own.

  • Make TypeScript your friend. Use TypeScript to your advantage, use it to enforce how you want people to consume your components. A good example on this is on our Button component. It has 2 props leftIcon and rightIcon. We have used TypeScript to make sure that only one of these is set otherwise it errors to the developer.

export type ButtonProps = BaseProps &
  (
    | {
        rightIcon?: ReactElement;
        leftIcon?: never;
      }
    | {
        rightIcon?: never;
        leftIcon?: ReactElement;
      }
  );
Enter fullscreen mode Exit fullscreen mode
  • Document your code and components. Use tools like storybook.

  • Have some sort of a style guide to make sure that you speak the same language with your team.

  • Write dump code. Keep your codebase straightforward and focused. Each piece of code should have a single, clear purpose.

  • Understand how things work under the hood. I published an article here after I found out how React checks if two values are the same.

Conclusion

We've explored some of the methods and tools I utilize. Although I haven't covered all the tools at my disposal, I suggest identifying what suits your particular requirements. It's advisable to stick to technologies you're skilled in, rather than adopting something solely for its novelty.

In the end, clients are most concerned with the final product, not the specific technologies you employ. Whether it's React, Vue, or another tool, prioritize the use of tools and workflows that enable quick deployment for the benefit of your users.

Resources

Top comments (25)

Collapse
 
mfp22 profile image
Mike Pearson • Edited

types is a terrible folder to have. Your folder structure should reflect your features, not the technologies they use. I wrote an article about this dev.to/mfp22/why-your-folder-struc...

Collapse
 
mattferrin profile image
Insight Lighthouse • Edited

This comment stood out to me because I created a types folder despite hating it only to politely act on code review comments of someone out of office. Because of a policy to resolve every code review comment.

But absolutely yes to the idea of grouping by feature first.

Collapse
 
josemukorivo profile image
Joseph Mukorivo • Edited

Thanks for the feedback Mike, I agree folders should reflect features but I think it makes sense to put some global types in folders like types. If you have a type like User that is used accross the entire app it might make sense to put it here but other types like Product for example can stay in their respective modules/features. What do you think?

Collapse
 
mfp22 profile image
Mike Pearson

User should be in a folder called user. Who says you can't import features into other features? We do that all the time.

I have never seen an example of a type that belonged in a global types folder. I actually think of types as the foundation for the folder structure, since they are the foundation of each feature.

Collapse
 
mfp22 profile image
Mike Pearson

Great article otherwise

Collapse
 
romeerez profile image
Roman K

Why put Next.js in title and promise to tell us about structuring, but not saying a word about the Next's app directory? App directory makes a big difference.

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Yes but a lot of NextJS projects are still on v12. For the app directory I recommend looking for somethings that says Next 13.

Collapse
 
r4e profile image
Richie Permana

This is basically a good example of your own expectation kills you.

Collapse
 
romeerez profile image
Roman K

Just an example of a misleading title. It doesn't kill you, nor makes you stronger.

Thread Thread
 
r4e profile image
Richie Permana

It's not even close to your so-called "misleading title". You're just assuming the OP is gonna talk about the present Next.js which is the 13 version. When in fact, there's nowhere about its gonna talked about latest Next.js.

ouch

Collapse
 
cmcnicholas profile image
Craig McNicholas

This was good and actually transfers a lot. We are Vue based but a lot of what's identified here can be applied to other projects e.g. we have an Nx monorepo, separation of projects by lib, microfrontend, UI, apps, TypeScript first, TDD, strong lining and naming conventions, document your conventions etc.

One thing I'd add to the above is a strong automated pipeline for PR's. When you have big long term projects those things save so much time.

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Thanks Craig, automated pipeline for PR's is a good addition.

Collapse
 
escnor profile image
Naveen • Edited

"If you are using CSS" what do you think is the best alternative?
Thanks Nice expalnation. Saved.

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Hi Naveen, for me I use tailwindcss most of the time. I have also tried CC-in-JS solutions, they were good but i find tailwind to be much faster.

Collapse
 
escnor profile image
Naveen

Thanks for replying

Collapse
 
nicolasdanelon profile image
NicolΓ‘s DanelΓ³n

bro! wow! awesome

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Thanks bro, glad you liked it.

Collapse
 
sip profile image
Dom Sipowicz

very impressive read!

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Thanks Dom

Collapse
 
danielgree profile image
Daniel Green

Next.js is a powerhouse, and your wisdom on frontend architecture is truly inspiring. Follow @danielgree

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Thanks Daniel.

Collapse
 
samucadev profile image
SamucaDev

Great post!

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Thanks SamucaDev

Collapse
 
only1adams profile image
Adams Muhammed

I use most of the tools here .. glad to know I been following the best practices.. this is an amazing article anyways πŸ’‘

Collapse
 
josemukorivo profile image
Joseph Mukorivo

Thanks Adams

Some comments have been hidden by the post's author - find out more