Basic React Project Setup
- web
- front
- react
Whenever starting a practice or small-scale project, similar setups are repeated. This is mostly because the most well-known technology stacks are chosen, with a few libraries added as needed.
Thus, I have organized the essentials for starting a project that typically uses React, TypeScript, code formatters, and react-router-dom. You can follow these steps directly.
1. Project Creation
The official React documentation recommends starting with a framework; however, at present, using only React is the most common approach.
While create-react-app provided a good boilerplate, it is no longer maintained. Nowadays, Vite has become standard for starting React projects.
Therefore, create a project using Vite that provides a React + TypeScript template. Based on experience, I found pnpm to be the fastest and most stable package manager, so I will use pnpm (while yarn berry is also good, pnpm seems to be more stable for now).
pnpm create vite project-name --template react-ts
2. Code Formatter
ESLint + Prettier, along with the emerging library Biome, can be used for formatting.
2.1. ESLint + Prettier
Install the tools necessary for formatting. Prettier is a library that allows operations to conform to ESLint rules. Libraries like @typescript-eslint/parser
and @typescript-eslint/eslint-plugin
are typically pre-installed.
pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier
Next, let's install eslint plugins. This includes a collection of plugins I used in previous projects along with eslint-config-airbnb
, which automatically applies Airbnb's JavaScript style guide.
pnpm add -D eslint-plugin-import eslint-plugin-react eslint-plugin-unused-imports eslint-config-airbnb eslint-plugin-jsx-a11y
Then, create a .eslintrc.cjs
file with the formatting rules as follows. These rules are a blend of lint files from Lee Chang-hee's blog and some Airbnb rules I have used personally.
// .eslintrc.cjs
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"airbnb",
"airbnb/hooks",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"plugin:import/recommended",
],
ignorePatterns: ["dist", ".eslintrc.cjs", "vite.config.ts"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: true,
tsconfigRootDir: __dirname,
},
plugins: ["react-refresh", "unused-imports"],
rules: {
"arrow-parens": ["warn", "as-needed"],
"comma-dangle": ["error", "always-multiline"],
"consistent-return": "warn",
"eol-last": ["error", "always"],
indent: ["error", 2],
"jsx-a11y/click-events-have-key-events": "off",
"jsx-quotes": ["error", "prefer-single"],
"keyword-spacing": "error",
"no-alert": ["off"],
"no-console": [
"warn",
{
allow: ["warn", "error"],
},
],
"no-duplicate-imports": "error",
"no-extra-semi": "error",
"no-param-reassign": "error",
"no-shadow": "off",
"no-trailing-spaces": "error",
"object-curly-spacing": ["error", "always"],
"padding-line-between-statements": [
"error",
{ blankLine: "always", prev: "*", next: "return" },
{ blankLine: "always", prev: ["const", "let", "var"], next: "*" },
{
blankLine: "any",
prev: ["const", "let", "var"],
next: ["const", "let", "var"],
},
],
"prefer-const": "error",
"prefer-template": "error",
quotes: [
"error",
"single",
{
avoidEscape: true,
},
],
semi: "off",
"space-before-blocks": "error",
"unused-imports/no-unused-imports": "error",
"import/extensions": "off",
"import/no-named-as-default": "off",
"import/no-unresolved": "off",
"import/order": [
"warn",
{
alphabetize: {
order: "asc",
caseInsensitive: true,
},
groups: [
"builtin",
"external",
["parent", "internal"],
"sibling",
["unknown", "index", "object"],
],
pathGroups: [
{
pattern: "~/**",
group: "internal",
},
],
"newlines-between": "always",
},
],
"import/prefer-default-export": "off",
"react-hooks/exhaustive-deps": ["warn"],
"react/jsx-boolean-value": "off",
"react/jsx-curly-brace-presence": [
"error",
{ props: "never", children: "never" },
],
"react/jsx-filename-extension": [
"warn",
{
extensions: [".js", ".ts", ".jsx", ".tsx"],
},
],
"react/jsx-no-bind": "off",
"react/jsx-no-useless-fragment": "warn",
"react/jsx-props-no-spreading": "off",
"react/no-array-index-key": "off",
"react/no-unescaped-entities": "warn",
"react/prop-types": "off",
"react/require-default-props": "off",
"react/self-closing-comp": "warn",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-misused-promises": [
"error",
{
checksVoidReturn: false,
},
],
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/semi": ["error"],
"@typescript-eslint/type-annotation-spacing": [
"error",
{
before: false,
after: true,
overrides: {
colon: {
before: false,
after: true,
},
arrow: {
before: true,
after: true,
},
},
},
],
},
};
However, with the current configuration, automatic fixes may not work properly. This is because the eslint file is not included in the tsconfig.json
. To resolve this, modify the include
field in the tsconfig.app.json
as follows. This file is referenced from tsconfig.json
and used during compilation.
{
/* compilerOptions omitted */
"include": ["src", "vite.config.ts", ".eslintrc.cjs"],
}
Even with this change, eslint.json may still not be properly recognized. This is due to Vite using the tsconfig.app.json
. This can be resolved by changing the parserOptions
in eslint.
// .eslintrc.cjs
module.exports = {
// omitted
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.app.json",
tsconfigRootDir: __dirname,
},
// omitted
}
Finally, create a prettier configuration file as follows. Create a .prettierrc.json
in the project root and write the following:
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 120,
"arrowParens": "avoid"
}
3. Alternative Code Formatter - Biome
3.1. Reason for Use
As an alternative, the newly released lint library Biome can also be utilized.
On July 9, 2024, while setting up a new project, I encountered a warning while installing eslint-plugin-unused-imports via pnpm.
Issues with peer dependencies found
.
└─┬ eslint-plugin-unused-imports 4.0.0
├── ✕ unmet peer @typescript-eslint/eslint-plugin@8: found 7.16.0
└── ✕ unmet peer eslint@9: found 8.57.0
Visiting the repository issue, the plugin creator recommended using Biome, stating that they started using it in their company and implied that the plugin is unlikely to be actively maintained.
So, I decided to give Biome a try. Biome is a library that sets up eslint and prettier all at once. An overview of its development can be found in Biome: Next Generation JS Linter and Formatter.
Let's install it according to the official documentation.
3.2. Installation and Application
pnpm add --save-dev --save-exact @biomejs/biome
Run the following command to create and initialize the configuration file.
pnpm biome init
Then, install the vscode Biome plugin. This plugin reads the Biome configuration file and automatically corrects the code.
Set the default formatter in the settings to Biome. However, as Biome is not yet widely adopted, other projects may still be using ESLint + Prettier. Thus, changing the vscode default formatter to Biome might be daunting.
Therefore, create a .vscode
folder within the project and create a settings.json
inside it with the following configuration:
{
"editor.defaultFormatter": "biomejs.biome"
}
The earlier eslint and prettier configuration files can be easily migrated to Biome via command line. Running the following commands will automatically migrate the eslint and prettier settings into biome.json
.
pnpm biome migrate eslint --write
pnpm biome migrate prettier --write
By doing this, configurations such as vite-tsconfig-paths
and various eslint plugins, as well as .eslintrc
and .prettierrc
files, will no longer be needed. Deleting them will lead to a cleaner package.json
.
I will share my experiences using Biome in a future article.
4. Additional Configurations
4.1. Import Alias
First, install vite-tsconfig-paths
to recognize the path alias from the tsconfig file in vite.config.ts
.
pnpm add -D vite-tsconfig-paths
Then, modify vite.config.ts
as follows:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths()],
});
This allows you to import using paths that start with @/
. Add the following paths
configuration to the compilerOptions
in tsconfig.app.json
.
{
"compilerOptions": {
/* omitted */
"paths": {
"@/*": ["./src/*"]
}
}
/* omitted */
}
4.2. React Router Dom
To implement basic routing, install react-router-dom
.
pnpm add react-router-dom
Then, set up the basic router in src/main.tsx
as follows:
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/about",
element: <div>about</div>,
},
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
Now run the development environment, and when you enter the /about
route, you will see a small page with the word "about," indicating that routing is correctly configured.
4.3. Vanilla Extract
When using other CSS-in-JS libraries like Tailwind or styled-components, Vanilla Extract can also be installed.
pnpm add @vanilla-extract/css
Follow the official documentation for Vanilla Extract's Integration - Vite. First, install the plugin.
pnpm add -D @vanilla-extract/vite-plugin
Then, add the plugin to vite.config.ts
as follows:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
import tsconfigPaths from 'vite-tsconfig-paths';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths(), vanillaExtractPlugin()],
});
References
Using ESLint Airbnb https://hayjo.tistory.com/111
Shadcn UI Official Documentation https://ui.shadcn.com/
Biome Official Documentation https://biomejs.dev/