I know that there are few questions about the react-redux project code structure but I am hoping to discuss a different approach. So the main libraries we are going to use are : webpack - react - redux -mocha - karma.
The classic folder structure for this seems to be:
js
├── actions
│ ├── action-for-componentA.js
├── components
│ ├── componentA.js
├── reducers
│ ├── reducer-for-componentA.js
└── stores ...
This seems to be what everyone else and all the generators out there are doing. But I feel like this is not the react way of structuring a project. The focus should be on the component and not on the individual constructs of react or redux. I would like to think about it in this way, when you need to change a button in PageX which is contained in a component hierarchy that starts with ComponentX -> ChildOfX - I should be able to traverse the directories in this exact same way.
Rather than having a components folder with all components thrown inside it I would rather have something like :
js
├── PageX
│ ├── action-for-pagex.js
├── componentX.js
├── containerX.js
├── reducerX.js
├── children
├── childrenC
├── childrenB
├── componentB.js
├── reducerB.js
├── ...
├── PageY
│ ├── ...
├── PAgeZ
│ ├── ...
This will be easier to traverse and it makes more sense when you think in "react". Can anyone see anything that might go wrong with this approach?
Related reading : http://engineering.kapost.com/2016/01/organizing-large-react-applications/
TL;DR
There is no standard or proper way structuring your application's code; it's mostly about your taste.
I will give you an answer due to my experience with React and Redux. Right now, I am involved in a huge project using the R&R stack. Our tech team had spent a great amount of time talking about folder structure since we should keep a linear and scalable way of coding.
Basically, we are using hybrid approach mixing the two examples your have provided. The second approach is referenced as "Folder structure by feature", the first one is called "Folder structure by type".
Since the React/Redux stack uses a standardised typeset of files it's quite easy to keep your application tree as tidy and scalable as possible.
Our technical team has agreed that:
Each Main Component stands out under the pages folder.
Each page may have some sub components as well.
Components are using a component.js entry point and may use a reducer as well
Sub components that are actually inherited and used from more than one parent components are placed under the shared folder
We also maintain some helper functions folder and a folder that holds some constants and utilities. This is mostly because our action dispatchers use common actions in our application lifecycle.
The application is bootstrap by a single container, a single store and simple entry point (app.js).
We use ES6 import statements to hold everything in place.
Here is a schematic version of our file structure:
constants
-- const.js
-- const2.js
helpers
--helperfunc1.js
shared
--Shared Element1
--- component.js
--- reducer.js
--Shared Element2
--- component.js
--- reducer.js
specs
-- spec1.js
-- spec2.js
pages
-Page1
-- Subpage1
--- component.js
--- reducer.js
-- Subpage2
--- component.js
--- reducer.js
-- component.js
-- reducer.js
-Page2
-- component.js
-- reducer.js
...
container.js
app.js
reducer.js
As we kept coding, adding features and maintaining our application, we wrote down some pros of this approach:
- Filenames are pretty short and easy to read.
- The file structure is easy to follow.
- Testing has made damn simple as importing the component and the reducer from a folder
- Naming bottlenecks have been thrown away.
- We had less naming issues and under the same folder.
- Once more, TableDataComponent.js is more verbose and hard to follow than table/component.js.
- Since React nested components are an essential part of the framework, the folder structure tracks down this logic. Inner levels of code are inherited from the upper rendered elements.
- Action reducers are also smoothly bootstrapped .
I would suggest spending some time reading this wonderful Reddit thread and this article as well, some awesome points are mentioned above.
I've came up with this structure and it works pretty well
Related
There are SSR-related problems with several pages in Next.js project that results in errors on npm run build and prevent the project from being built:
pages/
foo/
bar/
[id].jsx
index.jsx
index.jsx
...
For example, bar:
export function getStaticProps() {
return someApiCallThatCurrentlyFails()
...
}
export default function Bar() {...}
As a quick fix, it may be convenient to just not build bar/*.* pages and make routes unavailable.
Can pages be ignored on Next.js build without physically changing or removing page component files in the project?
You can configure the pageExtensions in the next.config.js.
// next.config.js
module.exports = {
pageExtensions: ["page.js"],
}
After configuring this, the only pages with *.page.js will be considered in the below given directory structure.
pages/
├── user
│ └── setting
│ ├── index.js
├── _app.page.js
├── _document.page.js
├── list.page.js
└── theme.ts
Custom file ignores patterns that are not supported yet. You can visit the PR created here, and the solution given here. This is the most satisfactory solution so far.
#Mathilda Here from Nextjs docs: it's necessary for all pages including _app, _document, etc.
https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions
Changing these values affects all Next.js pages, including the following:
- middleware.js
- pages/_document.js
- pages/_app.js
- pages/api/
For example, if you reconfigure .ts page extensions to .page.ts, you would need to rename pages like _app.page.ts.
When I use Sass files in NextJs, I am getting 'conflicting order' warnigs from mini-css-extract-plugin. The conflict always messes up my stylings on build. You can see the error described in the following link:
https://medium.com/iryl/control-css-imports-order-for-next-js-webpack-based-production-applications-3b69765444fd
This is an article with a solution to this issue but it talks about ordering Sass files for only one page. I'm not sure how to do order when there are multiple pages. How can I tackle the conflicting order issue?
if you are using nextjs 9.3 or higher you can use it like css modules. At least this is how they recommend that you do it.
This is an example.
You can also check the Sass support on the Next.js docs.
-- Update
The best way of do it it's by creating a different scss module for each component. It will look something like this.
Components
├── Header
│ ├── Header.js
│ ├── Header.module.css
├── Footer
│ ├── Footer.js
│ ├── Footer.module.css
├── Nav
│ ├── Nav.js
│ ├── Nav.module.css
The main idea of using css modules it's to prevent you from use a global sheet. this way the css will be optimized on the code splitting by components and you don't have to worry about declaring the same class twice, each css module file will generate a unique class name.
I'm working on a project using css modules, the project isn't done yet but the file structure it's almost the same working with sass, you can give it a look if you want.
https://github.com/edgarlr/portfolio/tree/main/components
There are plenty of questions and tutorials on this topic, but none of them cover all use cases for a chrome extension, because most of them assume there's only one entry point.
Here are the requisites:
Multiple "single page applications":
1) popup.html for the extension pop up page
2) options.html for the options page
3) custom.html this is a custom .html file that the extension can refer to "locally"
Each of these files are entry points for React to manipulate the DOM, but they behave independently of each other.
Non React TypeScript files
They must not be bundled together with any other scripts, and gets compiled to its own JavaScript file, for example a background.ts that compiles to background.js (which is refered to in the manifest.json).
I assume this is doable with TypeScript, React and Webpack but I'm not sure how to approach that.
There is a custom CRA template that exactly fits your needs: complex-browserext-typescript.
Usage:
npx create-react-app my-app --template complex-browserext-typescript
By default it sets up 4 entry points associated with page-like extension components:
popup (src/index.tsx) - extension's popup page, replaces the
default index entry point.
options (src/options.tsx) - extension's options page.
background (src/background.ts) - background script.
content (src/content.ts) - content script.
However there is an option to exclude any of the above components except the popup from compilation as well as add extra HTML page(s).
Also see this article for usage example.
I found a solution but it was not using create-react-app and webpack. It looks like parcel supports multiple entry points out of the box without any configuration.
Assuming a directory structure like this:
├── chrome
│ └── background.ts
├── html
│ ├── custom.html
│ ├── options.html
│ └── popup.html
├── manifest.json
├── package.json
├── react
│ ├── custom.tsx
│ ├── options.tsx
│ └── popup.tsx
With Parcel.js you simply do:
parcel build html/*.html react/*.tsx chrome/*.ts
And it will handle the multiple entry points. I created a repository that contains a template for that approach, it contains a fully runnable example.
I have a React site currently in JavaScript/ES that we are converting to TypeScript (🎉). We use the Container and Presentational breakdown, so each component will have either a Container and a Presentational file, or for simpler components just a Presentational file.
Over the course of a build, we will typically build "static" Presentational screens/components first, get them to pass design QA, and then make them dynamic by adding a Container. That means that the Presentational files are usually built first. Containers are built later, if needed.
Our directory structure looks like this:
components
├── SomeComponent
│ └── index.js
│ └── index.jsx
├── SomeOtherComponent
│ └── index.jsx
^^ Here we have SomeComponent which has Container+Presentational, while SomeOtherComponent has only Presentational.
This structure provides us with several benefits:
Imports are short and not "doubled": import SomeComponent from '../screens/SomeComponent' (not import SomeComponent from '../screens/SomeComponent/SomeComponent')
Imports do not need to change when Containers are eventually added. If only a Presentational .jsx file exists, the import above works. When/if a Container .js file is added, it then takes precedence over the .jsx, so the same import now targets the new file by virtue of it existing. (Webpack resolve.extensions = ['.js', '.jsx'])
No need to rename the presentational component file when a Container is added. This keeps git history nicely intact.
Quick & easy visual identification of which file is Container vs Presentational. Presentational is .jsx because that's where our JSX lives.
We use react/jsx-filename-extension to enforce that JSX should only be in .jsx files. This gives our devs get an eslint warning if they try to write JSX in a .js Container file. This enforces the concept that Containers should do business logic (in JS), while Presentationals should just output "dumb" markup (in JSX).
Works great!
The only caveat, which in my opinion is very minor, is: In order for this to work, in our Containers we import the Presentational like so: import SomeComponent from './index.jsx'. I say this is a caveat simply because it's the only place where we import using a complete file extension. No big deal...
In TypeScript, I'd love to just change those file extension from js/jsx to ts/tsx like so:
components
├── SomeComponent
│ └── index.ts
│ └── index.tsx
├── SomeOtherComponent
│ └── index.tsx
But we have a problem: In TypeScript we cannot use file extensions in an import (to the best of my knowledge). So this breaks our ability to execute the "caveat," and seems to force us to have different filenames for the Containers vs Presentationals. 😭
How does your team solve this dilemma?
I have searched for conventions on how other teams navigate this issue, and have come up empty-handed.
I am open to criticism of any part of our convention here, and I invite discussion. This is a higher-level issue that also affects the lower-level day-to-day of our devs. All ideas are good ideas (except the ones that suck)!
A few ideas:
A) ts-ignore
Use // #ts-ignore above the import SomeComponent from './index.tsx' line to mute the error. This feels like bad practice, but what would be the consequences?
Pros:
Allows use of index.ts and index.tsx to mimic our previous setup
No need to rename presentationals when a container is added
No need to change imports when a container is added
Cons:
Feels like bad practice (🚩)
Have to remember to add it and instruct other devs to add it
Requires we add '#typescript-eslint/ban-ts-ignore': 'off' to our eslint conf, which by extension means eslint will no longer prevent people from using #ts-ignore anywhere they want. 😬
What else might this break? Does it kill intellisense? What other havoc would this wreak?
B) PascalCase Presentationals
components
├── SomeComponent
│ └── index.ts
│ └── SomeComponent.tsx
├── SomeOtherComponent
│ └── index.tsx
Pros:
Seems kindof conventional. Some teams use PascalCase for all component names as convention.
No need to change imports when a container is added.
Cons:
Requires renaming presentational file when container is added (messes with git history).
Not as easy to instantly identify which file is container vs presentational. There's a (small) logical leap that must be taken in your mind: "That one is named after the component and when they are named after a component they are presentational").
The two Container/Presentational files will not always be "next to" eachother in the directory. If another file named index.test.ts or index.stories.tsx or styles.js exists, it might be in-between index.ts and presentational.tsx
C) presentational.tsx
components
├── SomeComponent
│ └── index.ts
│ └── presentational.tsx
├── SomeOtherComponent
│ └── index.tsx
Pros:
No need to change imports when a container is added.
Instantly obvious which file is presentational. (Solves problem with B above)
Cons:
Requires renaming presentational file when container is added (messes with git history).
The two Container/Presentational files will not always be "next to" eachother in the directory. If another file named index.test.ts or index.stories.tsx exists, it will be in-between index.ts and presentational.tsx
D) index.presentational.tsx
components
├── SomeComponent
│ └── index.ts
│ └── index.presentational.tsx
├── SomeOtherComponent
│ └── index.tsx
Pros:
No need to change imports when a container is added.
Instantly obvious which file is presentational. (Solves problem with B above)
The two Container/Presentational files will always be "next to" eachother in the directory. (Solves problem with B & C above)
Cons:
Requires renaming presentational file when container is added (messes with git history).
Long filename (not sure I really care about this)
learning javascript development using next.js. I have created a skeleton project, with following structure. what is the best way for development. create model, controllers? where should be the model and controller folders be? or is there a different way to do this in react world?
app
> .next
> node_modules
> pages
> api
> app.js
> public
> src
TL;DR Since nextjs does not impose conventions at this level, it's totally up to you and your team to decide what architecture to adopt and how to implement it and organize it. You can draw inspiration from an MVC-like framework like https://blitzjs.com/, which successfully brings a lot of concepts from the now old and boring Ruby on Rails.
If you think about it, MVC on the server-side fits very well with nextjs.This is how I look at it from a classic Model-View-Controller perspective:
A Controller manipulates your model based on user interaction, stiching together the results and passing them to the view.
In nextjs' terms, pages do fit very well this responsibility, although they do a bit more than that. You can put models wherever you want, lots of entry-level tutorials drop them under lib/.
Consider this example where I've got a page to show users profiles, in this case I'm also interested in doing Static Site Generation. Side note, I'm writing this off the top of my head, so be aware of copy and pasting to run it, it will prolly not work.
// under app/pages/users/[userId].js
import UserProfile from 'components/user-profile';
import User from 'models/user';
export default UserProfile;
// both at request time and build time, preps the props passed to the View.
export const getStaticProps = async ({params}) => {
const user = await User.find(params.id);
return {
props: { user }
}
}
// instructs next to render all user profiles using SSG
export const getStaticPaths = async () => {
const users = await User.findAll();
const paths = users.map(user => `/users/${user.id}`);
return { paths, fallback: false };
}
My team and I like to go a step further, and follow this structure, although we do not interface a database, but the file system and a few third party APIs over REST and GraphQL. Little secret: we tell nextjs which files to look for to load pages, otherwise it goes a bit cray cray.
project
└── app
├── components
│ ├── user-profile.test.tsx
│ └── user-profile.tsx
├── models
│ └── user.ts
│ └── user.test.ts
└── pages
└── users
├── [userId].page.ts
├── get-static-paths.test.ts
├── get-static-paths.ts
├── get-static-props.test.ts
└── get-static-props.ts
React is not an MVC framework, so architecturally it's a bit different. You can still structure it in a MVC way if you prefer, follow flux / redux patterns etc. Something to note that's different about react is it's unidirectional data flow unlike MVC where data is bound between the view and controller.
You also mentioned just starting to learn JS, I would recommend learning the fundamentals of javascript before diving into next.js, maybe even start with react. There are some things to consider when developing a server rendered app, but next definitely does a good job at making it easy.
Next.js also has their site open sourced here: https://github.com/zeit/next-site
I haven't looked at the code too much, but there are some patterns you could look for there.