Next.js is a popular React framework that is commonly used for building complex, dynamic web applications. In this tutorial, I will explain how I typically structure my Next.js applications to ensure maintainability, scalability, and organization.
- Organizing folders and files:
First, let’s start by setting up the basic folder structure of our Next.js application. Here is an example of how you can organize your files and folders:
- pages/ (contains all of your routes and components)
- index.js
- about.js
- contact.js
- components/ (contains reusable components)
- Header.js
- Footer.js
- styles/ (contains global styles and CSS modules)
- global.css
- index.module.css
- public/ (contains static assets like images and fonts)
- logo.png
- utils/ (contains utility functions and helper files)
- api.js
- auth.js
- Creating pages:
Next.js uses the concept of pages to handle routing. Each file in the pages/
directory represents a route in your application. For example, if you create a file called about.js
in the pages/
directory, you can access it at /about
in your browser.
Each page file in Next.js should export a React component that represents the content of that page. Here is an example of what a typical page file might look like:
// pages/about.js
import React from 'react';
const AboutPage = () => {
return (
<div>
<h1>About Us</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
);
};
export default AboutPage;
- Creating components:
Next.js allows you to create reusable components that can be shared across multiple pages. These components can be placed in the components/
directory. Here is an example of what a typical component file might look like:
// components/Header.js
import React from 'react';
const Header = () => {
return (
<header>
<h1>My Next.js App</h1>
</header>
);
};
export default Header;
You can then import and use this component in your page files like so:
// pages/index.js
import React from 'react';
import Header from '../components/Header';
const HomePage = () => {
return (
<div>
<Header />
<h1>Welcome to my Next.js App!</h1>
</div>
);
};
export default HomePage;
- Styling:
Next.js supports both global styles and CSS modules for styling your components. Global styles can be placed in the styles/
directory and imported into your application’s main file (e.g., pages/_app.js
). CSS modules can be used to create scoped styles for individual components. Here is an example of how you can use CSS modules in Next.js:
/* styles/index.module.css */
.container {
max-width: 1200px;
margin: 0 auto;
}
.title {
font-size: 24px;
color: blue;
}
// components/Footer.js
import React from 'react';
import styles from '../styles/index.module.css';
const Footer = () => {
return (
<footer className={styles.container}>
<p className={styles.title}>© 2022 My Next.js App</p>
</footer>
);
};
export default Footer;
- Using utility functions:
Next.js applications often require helper functions and utility files to handle tasks like fetching data from an API, managing authentication, or handling form submissions. You can place these utility functions in the utils/
directory and import them into your components and pages as needed. Here is an example of how you can create a utility function for fetching data from an API:
// utils/api.js
export const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
return response.json();
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
};
// pages/index.js
import React, { useEffect, useState } from 'react';
import { fetchData } from '../utils/api';
const HomePage = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then((data) => setData(data));
}, []);
return (
<div>
<h1>Welcome to my Next.js App!</h1>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
};
export default HomePage;
By following this structured approach to organizing your Next.js application, you can ensure that your codebase remains clean, maintainable, and easy to navigate. Additionally, separating your code into distinct folders and files can help improve collaboration among team members and streamline the development process.
A real stuff! Text a lot
Great video! I've been thinking about how I could use clean architecture in NextJS. You even touched upon a few concepts from Domain Driven Design in there. Cheers!
Instead of calling multiple use cases direcly inside your server components you might want to do it from a single controller instead 🙂 But great video.
should there also be services after use cases ? Use cases will call diffrent services and the services will call the data access
What if i already have a backend service, should i just remove the usecase and repository layer and call the backend api directly?
Can you give me the example repo
what is your vscode theme name?
I understand this abstracts/separates the 3 layers but could you just handle all of your business logic by chaining another zsa procedure say to check the group and pass it as context to the action etc ? I'm guessing this is inconvenient if you want to get group/role for other purposes/areas in the app?
Dislike the vid because you didn't upgrade those extensions and the line wasn't straight
I knew something was wrong with how I was coding but didn't know what it was, this video is what I needed – ty!
How do you handle transactions if you have multiple data access calls? Like how do you keep things still de-coupled?
Yea! I think your the only person talking about this topic in Nextjs community
Hii Cody, I like this architecture type, everything gets way cleaner and its kinda looks like a backend architecture with Controller and Services, I love it !
I have one question, inside the data-access & uses-cases files do we need to put « use server » ? Or maybe import server-only ? I thought it was necessary 🤔 (even tho I don’t know the difference between both of them lmao)
A question, say you have a usecase where you'll need to perform a db transaction involving multiple tables, how should this be implemented?
In the past, you would have recommended to do dependency injection on the use-cases or business logic so that it's not dependent on any external libraries (in this case drizzle-orm). Would you say injecting dependencies is just more cumbersome now and you're okay to have your business logic tied to the database repository functions or would you still dependency inject? I'm asking because I'm having the same issue
Thanks Cody! This is so useful!
16:45 would you also recommend co-locating your unit-tests? So your unit tests would be in the same directory as the function that it is testing.
Great stuff. Can you link to the safe server action package
awesome
Would love to see github repo of this, it's hard for me to comprehend the structure just by looking at the video