How To Set Up A New Web Project
A step-by-step guide to initiating and configuring your web project from the ground up.
Introduction
Creating a new web project—whether for learning, a hobby, freelance work, or a company—is a task you would often undertake as a developer. Not having a definitive system or guide to follow, can make this process inefficient and inconsistent. While a CLI (command line interface) that automatically scaffolds a project can be helpful, each project's needs are unique, and relying solely on such tools may not always be the best approach. Understanding the processes involved in starting a project from scratch is valuable, as it would provide you with foundational knowledge that can be useful when creating a customized approach tailored to your project's specific needs.
This article provides a step-by-step guide on initiating and configuring a new web project from scratch using the Remix web framework. While you might often use the CLI provided by a library or framework for initialization, this guide goes beyond that. It covers customizing your project setup to meet specific requirements, integrating various tools and practices for a streamlined development process, and applying best practices for version control, dependency management, and continuous integration.
Prerequisite
This article assumes you have a basic understanding of building websites using HTML, CSS, and JavaScript and are familiar with working in the terminal (or "command line"). If not, I recommend familiarizing yourself with these technologies before proceeding.
Version control
Version control is a system that manages changes to a project's files over time. A major feature of version control is that it ensures any changes made to your project are trackable and reversible. As a solo developer, you may not see the need for version control when you're not collaborating with other developers or your project isn't open-source. But, the ability to track the changes you've made over time and reverse those changes to a previous version when needed makes it a worthwhile decision for every developer. Git is a popular version control system, and GitHub is a popular platform for hosting and managing Git repositories. Together, they make version control easy for developers.
Step 1: Create an empty Git repository
To create a Git repository, you need to have Git installed on your system. Run the following command in your terminal to check if Git is installed:
If the output indicates that git
is an unknown command, refer to this GitHub article on how to install Git. Otherwise, run the following commands to create a new directory, replacing setup-web-project
with your project's name, and initialize it as a Git repository:
Now, make your first commit:
Your setup-web-project
directory structure should now look like this:
.
└── README.md
Step 2: Create a GitHub repository and connect it to your local repository
To create a repository on GitHub, you need a GitHub account. Create a GitHub account if you don't have one yet. Now, create a new GitHub repository named setup-web-project
. Refer to this GitHub article on creating a new repository if you need help.
After creating your new repository, your GitHub repository page should show a quick setup screen:
Copy and run the command under ...or push an existing repository from the command line. The code should look like this, replacing udohjeremiah
with your GitHub username:
After running the commands successfully, refresh the page of your repository on GitHub. The screen should now display the contents of your repository:
JavaScript runtime and package manager
A JavaScript runtime is an environment that allows JavaScript to be executed outside the browser, providing the necessary tools and APIs. The most well-known JavaScript runtime is Node.js. A package manager automates installing, updating, configuring, and managing software packages. npm is the default package manager for Node.js applications. With Node.js, developers can use JavaScript outside the browser, and npm enables them to publish their packages or install packages created by others from the npm registry, facilitating code reuse and sharing within the developer community.
Step 3: Initialize your Git repository as a new npm package
To initialize an npm package, you need to have Node.js and the npm CLI installed on your system. Run the following commands in your terminal to check if Node.js and the npm CLI are installed:
If the output indicates that node
or npm
is an unknown command, refer to this npm article on installing Node.js and npm. Otherwise, run the following command to initialize your Git repository as a new npm package:
After running the command successfully, a new file named package.json
will be created in your setup-web-project
directory. The default content of the file will look like this:
The npm init -y
command initializes a new npm package by creating a package.json
file with default values. Since this is a web project rather than a typical npm package for others to install, you should edit the package.json
file as follows:
Your setup-web-project
directory structure should now look like this:
.
├── README.md
└── package.json
Finally, stage, commit, and push your changes to GitHub with the following commands:
Dependencies and development dependencies in npm
Dependencies are packages that a project needs to run in production. These are essential for the project to function properly when it's deployed. They are installed with the command npm install <package-name>
and listed under the "dependencies"
section in the package.json
file. Development dependencies are packages only needed during the development phase, as they are not required for the application to run in production. They are installed with the command npm install -D <package-name>
and listed under the "devDependencies"
section in the package.json
file.
Step 4: Install the runtime dependencies for a Remix app
Run the following command to install the dependencies for a Remix app:
Did you notice that your setup-web-project
directory now contains a package-lock.json
file and a node_modules
directory you did not create? The package-lock.json
file is automatically generated to lock the versions of installed packages, ensuring consistent installs. The node_modules
directory, also automatically added by npm, contains all project dependencies and can become very large. Therefore, it is unnecessary to include it in the GitHub repository. Dependencies can be reinstalled using the project's package.json
file with npm install
.
To prevent a file or directory from being tracked by Git and pushed to GitHub from your local repository, you can ignore it by adding it to a .gitignore
file. Run the following command to create a .gitignore
file and ignore the node_modules
directory:
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .gitignore
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
└── package.json
Finally, stage, commit, and push your changes to GitHub with the following commands:
Step 5: Install and set up the build tools for a Remix app
Run the following command to install the build tools for a Remix app:
Since Remix uses Vite to compile your application, providing a Vite config file with the Remix Vite plugin from @remix-run/dev
is necessary.
Run the following command to create a vite.config.ts
file:
Copy the following code into the vite.config.ts
file:
Add the following scripts to the "scripts"
property in the package.json
file:
npm run dev
: is used to start the development server.npm run build
: is used to build the app for production.npm run start
: is used to start a production server.
Update your .gitignore
file to ignore the build
directory created whenever you run the command npm run build
. This directory contains the highly optimized production build for deployment and should not be included in your GitHub repository:
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .gitignore
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
└── vite.config.ts
Finally, stage, commit, and push your changes to GitHub with the following commands:
Step 6: Install and set up TypeScript for type safety
Run the following command to install TypeScript:
Run the following command to create a tsconfig.json
file to indicate that setup-web-project
is the root of a TypeScript project:
Copy the code from Remix's tsconfig.json
template to the tsconfig.json
file to specify the files to be included in the compilation and the compiler options required to compile the project:
Install the type definitions for react
and react-dom
:
Install the vite-tsconfig-paths
package to give Vite the ability to resolve imports using TypeScript's path mapping:
Update your vite.config.ts
file to add the vite-tsconfig-paths
plugin:
Add the typecheck
script to the "scripts"
property in the package.json
file:
npm run typecheck
: is used to check the type safety of the program, ensuring it's free of type-related errors.
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .gitignore
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
├── tsconfig.json
└── vite.config.ts
Finally, stage, commit, and push your changes to GitHub with the following commands:
Step 7: Install and set up Tailwind CSS for styling
Run the following command to install Tailwind CSS and its peer dependencies:
Run the following command to generate tailwind.config.ts
and postcss.config.js
files:
Copy the code from Remix's tailwind.config.ts
template into the tailwind.config.ts
file to add the paths to all of your template files:
Your setup-web-project
directory should now look like this:
.
├── README.md
├── .gitignore
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
├── postcss.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
Finally, stage, commit, and push your changes to GitHub with the following commands:
Step 8: Create the root and index routes
In Remix, app/root.tsx
is the root layout of your entire app and is called the "Root Route." It is the first component in the UI that renders, so it typically contains the global layout for the page.
Run the following commands to create the Root Route:
You need a tailwind.css
file inside the app
directory. This file will include the @tailwind
directives for each of Tailwind's layers.
Run the following command to create a tailwind.css
file inside app
:
Copy the following code into the app/tailwind.css
file:
Copy the following code into the app/root.tsx
file so that all other routes will render inside the <Outlet />
:
In Remix, any JavaScript or TypeScript files in the app/routes
directory will become routes in your app. The filename maps to the route's URL pathname (app/routes/about.tsx
matches the route /about
, app/routes/contact.tsx
matches the route /contact
), except for _index.tsx
, which serves as the index route for the root route, matching the route /
.
Run the following commands to create the index route for the root route:
Copy the following code into the app/routes/_index.tsx
file:
Now, run the following command to start the dev server:
Open http://localhost:5173 in your browser, and you should see a screen that looks like this:
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .gitignore
├── app
│ ├── root.tsx
│ ├── routes
│ │ └── _index.tsx
│ └── tailwind.css
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
├── postcss.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
Finally, stage, commit, and push your changes to GitHub with the following commands:
Step 9: Install and set up ESLint for linting
Linting is analyzing code to catch potential errors and enforce coding standards. ESLint is the most popular linting tool for JavaScript, offering customizable rules for maintaining code quality and consistency.
Run the following command to install ESLint and React-specific plugins for ESLint:
Run the following command to install TypeScript-specific plugins:
Run the following command to create a .eslintrc.cjs
file for your ESLint configuration:
Copy the code from Remix's .eslintrc.cjs
template into the eslintrc.cjs
file:
Add the following scripts to the "scripts"
property in the package.json
file:
npm run lint
: is used to check the program for linting errors, ensuring it adheres to ESLint's style.npm run lint:fix
: is used to automatically fix linting errors in the program that ESLint can resolve.
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .eslintrc.cjs
├── .gitignore
├── app
│ ├── root.tsx
│ ├── routes
│ │ └── _index.tsx
│ └── tailwind.css
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
├── postcss.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
Finally, stage, commit, and push your changes to GitHub with the following commands:
Step 10: Install and set up Prettier for code formatting
Code formatting involves organizing code consistently according to predefined style rules, improving readability and maintainability. Prettier is the most popular code formatting tool for JavaScript. It automatically formats code to ensure a consistent style across projects.
Run the following command to install Prettier:
Prettier is an opinionated code formatter. It applies a default set of rules to your code regardless of any configuration. But, it does not handle code sorting out of the box. To enable this feature, you need to use a plugin.
Run the following command to install the Tailwind CSS plugin that sorts its utility classes and Trivago's plugin that sorts import declarations:
Run the following command to create a prettier.config.js
file for your Prettier configuration:
Copy the following code into the prettier.config.js
file:
Add the following scripts to the "scripts"
property in the package.json
file:
npm run format
: is used to check the code format of the program, ensuring it adheres to Prettier's style.npm run format:fix
: is used to automatically fix code formatting errors in the program.
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .eslintrc.cjs
├── .gitignore
├── app
│ ├── root.tsx
│ ├── routes
│ │ └── _index.tsx
│ └── tailwind.css
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
├── postcss.config.js
├── prettier.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
Finally, stage, commit, and push your changes to GitHub with the following commands:
Step 11: Install and set up git hooks
Git hooks are scripts that Git executes automatically before or after a particular event (such as commit, push, merge, etc) occurs in a Git repository. They are used to automate tasks, enforce policies, ensure consistency, etc. In the JavaScript ecosystem, husky is a popular tool that makes managing Git hooks easy, and lint-staged is a popular tool used to run linters on files staged for commit. Together, husky and lint-staged can be used to automate tasks that run before the commit process.
Run the following command to install husky and lint-staged:
Update the package.json
file to include the "lint-staged"
configuration as follows:
Now, run the following command to set up husky in the project:
After running the command successfully, a .husky
directory with a pre-commit
file will be created, and the prepare
script will be added to the "scripts"
property in the package.json
file:
Update the .husky/pre-commit
file to:
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .eslintrc.cjs
├── .gitignore
├── .husky
│ ├── _
│ │ └── ... (contains more directories and files)
│ └── pre-commit
├── app
│ ├── root.tsx
│ ├── routes
│ │ └── _index.tsx
│ └── tailwind.css
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
├── postcss.config.js
├── prettier.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
Now, stage and commit your changes with the following commands:
Oops 😬, we encountered an error. I apologize for the inconvenience. You just saw the power of Git hooks in action. Currently, our codebase does not adhere to Prettier's style. Although we have set up Prettier, we have not yet formatted the codebase. So, when attempting to commit our changes, the pre-commit Git hook is automatically triggered before the commit process. The specified tests in the Git hook failed, leading to the interruption of the commit process:
As indicated in the error message (see photo above), we can use the --write
flag with Prettier to fix the code style issues automatically. We've already configured a script for this in our package.json
.
Run the following command to format the codebase to adhere to Prettier's style:
After successfully running the command, Prettier will format our files, making changes to the ones we staged last time. Therefore, we should stage the modifications and attempt to commit again:
Once again 😩! The commit process is interrupted but for good reasons. This time it's not Prettier, but ESLint that found a problem with our code:
Again, the error message tells us how to fix the issues. Go into the app/routes/_index.tsx
file and change all occurrences of '
with '
. Also remember, to run npm run format:fix
after making these changes, to make sure the codebase adheres to Prettier's style.
Run the following commands to stage the changes and attempt to commit again:
Hurray! 🎉 It worked. With that done, run the following command to push your changes to GitHub:
Continuous integration and dependency management
Continuous integration (CI) is a software development practice where developers frequently commit code changes to a central repository. Each commit is automatically checked to ensure it is fit for production by building the project and running tests to catch errors quickly. Dependency management involves handling the packages that your project depends on. It includes tasks such as installing, updating, and resolving dependencies to ensure your project has all the necessary components to function correctly. GitHub provides built-in CI and dependency management capabilities through GitHub Actions and Dependabot.
Step 12: Setup CI and dependency management with GitHub
For GitHub to discover any GitHub Actions workflows in your repository, you must save the workflow files in a directory called .github/workflows
. You can name the workflow file anything, but it must have a .yml
or .yaml
extension.
Run the following commands to create the .github/workflows
directory:
Previously, you added a pre-commit Git hook to your repository to ensure that your code is correctly formatted, free of linting or type-related errors and that the build process completes successfully before committing any staged files. Additionally, you can ensure these tests run whenever a pull request is opened or code is pushed to the main
branch on your hosted GitHub repository using CI. GitHub will run the CI tests and provide the results in the pull request, so you can see whether the changes in your branch introduce any errors. When all CI tests in a workflow pass, the changes you pushed are ready to be merged. If a test fails, one of your changes may have caused the failure.
Run the following command to create a ci.yml
file in the .github/workflows
directory:
Copy the following code into the .github/workflows/ci.yml
file:
Dependabot creates pull requests to keep your dependencies secure and up-to-date. To configure Dependabot to maintain your repository, you need a Dependabot configuration file, dependabot.yml
, stored in the .github
directory.
Run the following command to create the .github/dependabot.yml
file:
Copy the following code into the .github/dependabot.yml
file:
Your setup-web-project
directory structure should now look like this:
.
├── README.md
├── .eslintrc.cjs
├── .github
│ ├── workflows
│ │ └── ci.yml
│ └── dependabot.yml
├── .gitignore
├── .husky
│ ├── _
│ │ └── ... (contains more directories and files)
│ └── pre-commit
├── app
│ ├── root.tsx
│ ├── routes
│ │ └── _index.tsx
│ └── tailwind.css
├── node_modules
│ └── ... (contains more directories and files)
├── package-lock.json
├── package.json
├── postcss.config.js
├── prettier.config.js
├── tailwind.config.ts
├── tsconfig.json
└── vite.config.ts
Finally, stage, commit, and push your changes to GitHub with the following commands:
Git workflow
A Git workflow is a structured method for managing changes to a project's source code using Git. It defines how changes are proposed, reviewed, and integrated into the project, ensuring work is done consistently and efficiently. Various Git workflows exist, each tailored to different types of projects and teams. A popular workflow for continuously deployed software, such as web applications, is the GitHub Flow. GitHub Flow is a simplified workflow that uses only feature branches and the main
branch. All changes are merged into the main
branch via pull requests, and the main
branch is continuously deployed in small, incremental updates as changes are merged into it.
Step 13: Add a ruleset to the main
branch
To require a pull request and status checks to pass before merging, follow these steps:
- Refer to this GitHub article on creating a branch or tag ruleset for detailed instructions.
- Complete the steps outlined in the article.
After completing the steps from the "Creating a branch or tag ruleset" section, you should see a screen that looks like this:
- Under Ruleset Name, enter the value "GitHub Flow".
- Change the Enforcement status from "Disabled" to "Active".
- Under Target branches, click on the Add target button and select Include default branch.
- Under Branch rules, check the following options:
- Require a pull request before merging
- Require status checks to pass
After clicking on Require status checks to pass, an additional settings will be displayed:
- Check the option Require branches to be up to date before merging.
- Click on the Add checks button. In the displayed search input, type "Build". You'll see a list of suggestions. Click on Add Build (ubuntu-latest, 20.x).
- Finally, click the Create button to save your changes.
Testing it all
Congratulations 🎉! Your project configuration is complete, and you can now start developing your app. To get started, create a feature branch from the main
branch for your specific feature, and push this new branch to the remote repository. Switch to the feature branch, make your changes, and push them to the remote repository. Then, open a pull request on the main
branch to propose your changes. Once your pull request passes all CI tests, merge your changes into the main
branch and delete your feature branch (if it's no longer needed). To see this process in action, let's work on a feature together.
Create and push a feature branch from the main
branch
Run the following commands to switch to your main
branch, pull from the remote tracking branch to ensure main
is up-to-date with the latest changes in the remote repository, and create the complete-homepage
feature branch from it:
The complete-homepage
branch is now created (run the command git branch
to see it). But, the remote repository still does not have a copy of it. As a best practice, even before you start working on a feature, it's important to push this branch to the remote repository so that it has a copy. This informs other developers (if you're working on a team or your project is open-source) about the new feature you're working on, potentially avoiding conflicts with other developers unknowingly working on the same thing.
Run the following command to push the new branch to the remote repository:
After running the command successfully, you can check if the feature branch was pushed to the remote repository. To do this, go to your repository, click on the button displaying main with a dropdown icon, and you'll see a list of all the branches in the repository:
Make and push your changes
Run the following command to switch to the complete-homepage
branch so you can start working on your feature:
Next, update the app/routes/_index.tsx
file with these changes:
And that's it! Our (simplistic) feature to complete the homepage is done.
Run the following commands to stage, commit, and push your local changes to the complete-homepage
branch on the remote repository:
Before pushing new changes to a remote branch, it's important to run git pull
first. This will ensure that your local branch is updated with any new changes that may have been pushed to the remote branch by others, especially if you're working on a team or if your project is open source.
Open a pull request on the main
branch
Go to your repository, click on the button displaying main with a dropdown icon, and on the displayed list of branches, click the complete-homepage
branch. On the complete-homepage
branch, your screen should look like this:
To open a pull request, click on the Compare & pull request button or click on the button displaying Contribute with a dropdown icon, and click Open pull request from the displayed dropdown. On the next page, you're taken to, enter a description explaining the changes you've made. Finally, click the Create pull request button to open a pull request on the main
branch.
Merge your changes into the main
branch and delete your feature branch
After clicking the Create pull request button, you should be taken to a pull request page. Wait for your CI tests to complete. After your CI tests are completed, your screen should look like this:
Click on the Merge pull request button and then click Confirm merge. Once the merging is complete, you can delete your branch by clicking the Delete branch button if it's no longer useful.
If you delete your branch on GitHub, then run the following command to delete the complete-homepage
branch locally:
And that's it. Go build the next Facebook.
Conclusion
As a developer, you are often expected to use the automatic installation CLI provided by libraries or frameworks to set up new web projects. But, sometimes the default options provided by these CLIs may not fully meet your needs or may only offer a basic template that doesn't align with your desired project configuration. This article provided a comprehensive guide for customizing the initialization and configuration of your project, either from scratch or by enhancing the functionality provided by the automatic CLI of your library or framework.
You now have a guide to set up future projects efficiently, consistently, and in a timely fashion. Most importantly, you understand how each process works. You can either use the step-by-step guide as provided or use it as a foundation to customize and fit it to your own needs. In any case, the goal is to have a clear system or guide to follow whenever you start a new project.
Remember, "hackers hack, crackers crack, and whiners whine. Be a hacker." Take care.