Back

Blog

Unveiling the Anatomy of a React SPA: Naming Conventions, Directory Structure, and More

Web Development

May 12, 2023

Hey there, fellow React enthusiasts! Today, we're venturing into the nitty-gritty world of structuring a React Single Page Application (SPA). It's an often overlooked aspect of building applications, but it's one that can significantly impact the maintainability and scalability of your project.

I've been through countless React projects - some neatly organized, others, well, let's just say they were like a box of tangled Christmas lights. Based on these experiences, I'd like to share with you some conventions and strategies that have worked well for me. So, buckle up and let's dive right in!

Root Directory. The Starting Point

When you set up a new React project, say, using 'create-react-app', you'll find that the project's root directory contains a few key files and folders. Let's break these down:

  1. 'node_modules': This is where all the packages that your project depends on live. These packages are downloaded when you run 'npm install' or 'yarn install'. It's typically not recommended to modify this directory directly or check it into your source control.

  2. 'public': This folder contains static files that can be accessed publicly. The most important file here is 'index.html'. This is the only HTML file in a typical React SPA, and it's the file that gets served on your initial page load. Other static assets like images, fonts, or a manifest file for PWA setup might also be located here.

  3. 'src': This is where all your React code lives. It's the heart of your React application. Inside, you'll find your JavaScript (or TypeScript) files, CSS (or SCSS) files, tests, and any other assets that are part of your application and need to be processed by Webpack or other build tools.

  4. 'package.json': This is a crucial file in any Node.js-based project, including React. It lists the packages your project depends on, specifies commands for running tasks like starting the dev server or building the project for production, and includes some metadata about your project like its name and version.

  5. '.gitignore': This file tells Git which files or directories to ignore in your project. Typically, you'd ignore 'node_modules', as well as any environment variables and build artifacts.

  6. 'README.md': This is a markdown file where you'd typically document information about your project, like what it does, how to install it, how to use it, etc.

  7. 'package-lock.json' or 'yarn.lock': These files are automatically generated by npm or Yarn, respectively. They lock down the exact versions of the dependencies installed in 'node_modules' to ensure consistency across all environments.

There might be other configuration files at the root of your project as well, depending on the tools you're using. For example, a '.babelrc' file for Babel configuration, a '.eslintrc' file for ESLint configuration, or a 'jest.config.js' file for Jest configuration.

For the example, we'll look at a large React application and you might want a "feature-first" approach, where you group related code by features or domains. This is also known as "domain-oriented" or "module-oriented" design. Here's an example:

/my-app
|-- node_modules/
|-- public/
|   |-- index.html
|   |-- manifest.json
|-- src/
|   |-- features/
|   |   |-- User/
|   |   |   |-- components/
|   |   |   |   |-- UserProfile.js
|   |   |   |   |-- UserList.js
|   |   |   |-- services/
|   |   |   |   |-- userService.js
|   |   |   |-- tests/
|   |   |   |   |-- UserProfile.test.js
|   |   |   |   |-- UserList.test.js
|   |   |   |-- UserContext.js
|   |   |   |-- userReducer.js
|   |   |-- Product/
|   |   |   |-- components/
|   |   |   |   |-- ProductList.js
|   |   |   |   |-- ProductDetail.js
|   |   |   |-- services/
|   |   |   |   |-- productService.js
|   |   |   |-- tests/
|   |   |   |   |-- ProductList.test.js
|   |   |   |   |-- ProductDetail.test.js
|   |   |   |-- ProductContext.js
|   |   |   |-- productReducer.js
|   |-- shared/
|   |   |-- components/
|   |   |   |-- Button/
|   |   |   |   |-- Button.js
|   |   |   |   |-- Button.test.js
|   |   |   |   |-- Button.css
|   |   |   |-- Navbar/
|   |   |   |   |-- Navbar.js
|   |   |   |   |-- Navbar.test.js
|   |   |   |   |-- Navbar.css
|   |   |-- utils/
|   |   |   |-- helpers.js
|   |   |-- assets/
|   |   |   |-- images/
|   |   |   |   |-- logo.png
|   |   |   |-- fonts/
|   |   |   |   |-- custom-font.woff
|   |-- App.js
|   |-- index.js
|-- package.json
|-- package-lock.json
|-- .gitignore
|-- README.md

In this structure:

  1. Feature-specific code is encapsulated: Each feature or domain (e.g., User, Product) gets its own directory under 'features'. This directory contains everything related to that feature, including its components, services (e.g., API calls), tests, context provider, and reducer (if you're using something like Redux or useReducer for state management).

  2. Shared code is separated: Code that is shared across different features (e.g., reusable components, utility functions, static assets) is kept under a 'shared' directory.

This approach can be beneficial in large, complex projects because it:

  1. Improves code discoverability: When you're working on a feature, you know exactly where to find the code related to that feature.

  2. Encourages code sharing and reuse: By keeping shared code separate, you can more easily reuse it across different features.

  3. Facilitates parallel development and code ownership: Different teams or developers can work on different features without stepping on each other's toes. It's also clear who owns what code.

Remember it could vary based on your specific needs or the tools you're using. For example, if you're using TypeScript, you might have a 'tsconfig.json' file in your root directory for TypeScript configuration. Or if you're using SCSS, you might have a separate 'styles' directory for your SCSS files. The key is to find a structure and workflow that works best for you and your team.

SRC Folder. Where the Magic Happens

The 'src' directory is where all your React components and related code live. This is where you'll be spending most of your time as a React developer. It's the heart of your React application. Inside, you'll find your JavaScript (or TypeScript) files, CSS (or SCSS) files, tests, and any other assets that are part of your application.

Of course, as applications grow, the need for a more structured and modular catalog becomes apparent. Here is an example of the 'src' catalog for a large-scale React application:

/src
|-- features/
|   |-- User/
|   |   |-- components/
|   |   |   |-- UserProfile/
|   |   |   |   |-- UserProfile.js
|   |   |   |   |-- UserProfile.test.js
|   |   |   |   |-- UserProfile.module.css
|   |   |-- services/
|   |   |   |-- userService.js
|   |   |-- hooks/
|   |   |   |-- useUser.js
|   |   |-- UserContext.js
|   |   |-- userReducer.js
|-- shared/
|   |-- components/
|   |   |-- Button/
|   |   |   |-- Button.js
|   |   |   |-- Button.test.js
|   |   |   |-- Button.module.css
|   |   |-- Navbar/
|   |   |   |-- Navbar.js
|   |   |   |-- Navbar.test.js
|   |   |   |-- Navbar.module.css
|   |-- utils/
|   |   |-- helpers.js
|   |   |-- tests/
|   |   |   |-- helpers.test.js
|   |-- assets/
|   |   |-- images/
|   |   |   |-- logo.png
|   |   |-- fonts/
|   |   |   |-- custom-font.woff
|-- routes/
|   |-- Home/
|   |   |-- Home.js
|   |   |-- Home.test.js
|   |   |-- Home.module.css
|   |-- About/
|   |   |-- About.js
|   |   |-- About.test.js
|   |   |-- About.module.css
|-- hooks/
|   |-- useTheme.js
|-- App.js
|-- index.js

In this structure:

  1. 'features' directory contains different features or domains of the application. For each feature, we have specific components, services (for API calls), hooks, context providers, and reducers (if you're using Redux or the useReducer hook).

  2. 'shared' directory contains components, utilities, and assets that are shared across multiple features of the application.

  3. 'routes' directory contains the "page" components that represent different routes in your application. Each route has its own JavaScript, test, and CSS module file.

  4. 'hooks' directory at the root level of 'src' contains custom hooks that are used across multiple features of your application.

  5. 'App.js' is the root component of your application. This is where you'd set up your top-level routes and context providers.

  6. 'index.js' is the entry point of your application. This is where you render the root 'App' component into the DOM.

This structure allows for better task separation, modularity and reusability. It is more scalable for larger applications and makes it easier to navigate and understand the code base. It is important to regularly review the project structure as it grows and evolves.

Naming Conventions. Consistency is Key

Naming conventions are a critical part of any development process, not just in React, but in any programming language or framework. They're essential for code readability and maintainability, particularly in large codebases where multiple people are working.

Here are some best practices when it comes to naming conventions in a React project:

1. File and Directory Names

File and directory names should be descriptive and reflect the purpose of the contents. Here are some rules to follow:

  1. Use PascalCase for filenames or directory names, especially for components. For example, 'MyComponent.js' or 'MyComponent/' directory.

  2. Use 'camelCase' for non-component filenames, like 'myHelperFunction.js' or 'myService.js'.

  3. Tests files should be named as '<filename>.test.js'. For example, 'MyComponent.test.js'.

  4. If you're using CSS Modules, name your CSS files as '<filename>.module.css'. For example, 'MyComponent.module.css'.

2. Component Names:

React component names should always start with a capital letter. This is a requirement in JSX syntax because components starting with a lowercase letter are treated as built-in elements like '<div>' or '<span>'.

  1. Component names should be descriptive and convey their purpose. For example, 'UserProfile.js' or 'ProductList.js'.

  2. Avoid generic names like 'Component1' or 'DataDisplay'.

3. Variable and Function Names:

For variables and function names, you should follow the JavaScript naming convention, which is camelCase.

  1. Variable names should be nouns, such as 'const userCount = 5';.

  2. Function names should start with a verb to reflect the action they perform, such as 'function getUserData() {...}'.

  3. Boolean variables should be prefixed with is, has, can or similar, like 'const isLoading = true;'.

4. Prop Names:

Prop names should also be in camelCase. They should be descriptive and concise. Like variable names, Boolean props should be prefixed with 'is', 'has', 'can', etc.

5. Context:

If you're using the Context API, it's common to suffix your context with Context. For example, 'ThemeContext' or 'UserContext'.

6. Custom Hooks:

Custom hooks should always start with the word 'use' as per the React convention. For example, 'useTheme' or 'useFetch'.

In the end, the most important thing about naming conventions is consistency. Whichever rules you decide to follow, make sure they're applied consistently throughout your codebase. This will make your code more readable and easier to understand for anyone who works on it, including your future self.

Components. Breaking it Down 

Components are the building blocks of any React application. A component in React is a self-contained piece of UI that can manage its own state, render, and handle events. Components can be composed to build complex UIs. There are two main types of components in React: functional components and class components.

Functional Components: These are the simplest types of components. They're just JavaScript functions that take props as an argument and return JSX. For example:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Sure, let's take a closer look at React components.

Components are the building blocks of any React application. A component in React is a self-contained piece of UI that can manage its own state, render, and handle events. Components can be composed to build complex UIs. There are two main types of components in React: functional components and class components.

Functional Components: These are the simplest types of components. They're just JavaScript functions that take props as an argument and return JSX. For example:

jsxCopy codefunction Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Class Components: This structure allows for better task separation, modularity and reusability. It is more scalable for larger applications and makes it easier to navigate and understand the code base. It is important to regularly review the project structure as it grows and evolves.

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

In modern React development, functional components are more common thanks to hooks.

Components can be as small as a button or as large as an entire page. The key to structuring components in React is to make them reusable and maintainable. Here are some best practices:

  • Single Responsibility Principle: Each component should do one thing, and do it well. If a component starts to get too large or complex, it's probably a sign that it should be broken down into smaller, more manageable components.

  • Reusable Components: Components should be designed to be reusable as much as possible. This often means making them generic and passing data in via props.

  • Prop Types and Default Props: You should define 'propTypes' for every component. This makes it clear what types of props a component expects. You can also define 'defaultProps' for any props that have a default value.

  • Stateless and Stateful Components: Stateless components (also called "dumb" or "presentational" components) are components that just take props and render them. They don't manage or know about state. Stateful components (also called "smart" or "container" components) are components that manage state and pass it down to stateless components via props.

  • Component Folder Structure: Each component should have its own folder with all related files. For example:

/Button
|-- Button.js
|-- Button.test.js
|-- Button.module.css

Remember, these are guidelines, not strict rules. The structure of your components will depend on the specifics of your project and your team's preferences. The most important thing is that your structure is clear, organized, and scalable.

And that, my friends, is the beauty of a well-structured React project. Itmight seem trivial at first, but trust me, having a clean, organized project structure can make your life as a developer so much easier. It's like having a well-organized workspace - you know exactly where everything is, and it significantly boosts your productivity and efficiency.

Furthermore, it's not just about you. If you're working in a team, an organized structure makes collaboration smoother. Your teammates will thank you when they can find exactly what they're looking for without having to navigate through a labyrinth of files and folders.

Finally, remember that these are just guidelines and what I've found to work best in my experience. The beauty of software development is that there's no one-size-fits-all solution. What matters most is that your project structure works for you and your team, and that it can scale as your project grows.

Mark Zaicev

Lead Developer

Other articles

By continuing to use this website you agree to our Cookie Policy