Create your own
Lesson illustration

Setting Up a Next.js TypeScript Project with Code Quality and Branching Standards

Hello. In the previous lesson, you turned the job-application workspace MVP into user stories, testable acceptance criteria, and an ordered backlog. You now have a sensible answer to “what should I build first?” This lesson establishes the professional project baseline: a reproducible Next.js repository where quality checks and Git workflow support that backlog rather than slow it down.

By the end, you will have a public-ready local repository using Next.js, TypeScript, ESLint, Prettier, and a lightweight pull-request workflow. The next lesson will use this foundation to build and deploy the first responsive application shell.


The baseline you are creating

A repository is more than a folder containing working code. For a portfolio project, it should make three things clear:

  1. How to run the project
  2. How code quality is checked
  3. How changes are made without destabilizing the main branch

For this application, choose a deliberately small toolchain:

ConcernChoiceWhy
FrameworkNext.js App RouterCurrent React-oriented full-stack framework conventions; useful for frontend roles.
LanguageTypeScriptMakes application data and component contracts explicit.
StylingTailwind CSSMatches tools already present in your frontend experience and supports fast responsive implementation.
LintingESLint with Next.js and TypeScript rulesFlags common React, Next.js, and TypeScript problems.
FormattingPrettierApplies one consistent code layout without debates over spacing or line wrapping.
Version controlGit and GitHub pull requestsProduces reviewable evidence of incremental development.

It is important not to confuse the responsibilities of your checks:

  • Prettier formats code: indentation, wrapping, quotes, commas, and similar presentation decisions.
  • ESLint lints code: it detects suspicious patterns and applies project rules, including React and Next.js rules.
  • TypeScript type-checking verifies that the types in the program are internally consistent.
  • A production build verifies that Next.js can compile the application for deployment.

One tool cannot substitute for the others. In particular, formatting does not prove that your application is type-safe, and a successful build should not be treated as a replacement for deliberate linting.

Getting Started: Installation

Read the official Next.js installation guide to confirm the current scaffold choices, Node.js requirement, and built-in TypeScript support. Use it as the source of truth if the CLI wording differs slightly from this lesson.

In the “Create with the CLI” section, read the create-next-app introduction, then review the listed customization prompts. Next, in “Run the development server” and “Set up TypeScript,” read the TypeScript setup notes. Notice that Next.js generates and maintains a recommended tsconfig.json configuration rather than requiring you to invent one.


Scaffold the application intentionally

First, confirm that your Node.js version meets the current Next.js minimum requirement:

node --version
npm --version

The official documentation currently specifies Node.js or later. If your installed version is older, update Node.js before scaffolding rather than trying to repair dependency problems afterward.

Create the project from the directory where you keep development work:

npx create-next-app@latest job-application-workspace

When prompted, select these options:

PromptChoiceReason
TypeScriptYesThis project will model application data and UI state with types.
LinterESLintUse the established Next.js-aware linter.
React CompilerNo for nowIt is not needed to deliver this MVP; keep the initial setup understandable.
Tailwind CSSYesA practical, familiar styling system for the application shell.
Code inside src/ directoryYesSeparates source code from repository configuration and documentation.
App RouterYesThis is the recommended routing model for new Next.js applications.
Import aliasYes, retain @/*Avoids fragile chains of relative imports as the project grows.
Include AGENTS.mdYour choiceIt does not affect the runtime application. Keep it only if you expect to use coding agents and will review their changes carefully.

Then enter the project and verify that the initial application works:

cd job-application-workspace
npm run dev

Open http://localhost:3000. Make one harmless visible edit to src/app/page.tsx, save it, and verify that the browser updates. This checks the complete local loop: dependencies, development server, file watching, editor, and browser.

At this point, inspect the generated repository rather than deleting unfamiliar files. The most relevant starting files are usually:

File or directoryPurpose
src/app/page.tsxThe route component for the home page.
src/app/layout.tsxShared HTML structure and metadata for routes.
src/app/globals.cssGlobal styles and Tailwind imports.
tsconfig.jsonTypeScript compiler configuration, including the import alias.
eslint.config.mjsESLint flat configuration.
.gitignoreFiles that must not enter Git history, such as dependencies and build output.
package.jsonDependencies and project commands.

The @/* alias lets you write imports such as:

import { ApplicationCard } from "@/components/application-card";

That stays readable when the component is nested several folders away. It is a small convention, but it makes later refactoring less noisy.


Add formatting without weakening linting

A good project baseline makes the easy action the correct one. You should be able to format the repository, then run linting and type checks using memorable commands.

Install Prettier and the compatibility configuration:

npm install --save-dev prettier eslint-config-prettier

Prettier will own formatting. ESLint will own code-quality rules. eslint-config-prettier turns off any ESLint formatting rules that could contradict Prettier.

Configuration: ESLint

Read the official Next.js ESLint documentation before changing the generated config. It shows the current flat-config approach, the TypeScript extension, and the recommended way to prevent ESLint and Prettier from fighting each other.

In “Setup ESLint,” read the flat-config setup and compare it with your generated eslint.config.mjs. In “With TypeScript,” read the TypeScript configuration. Finally, in “With Prettier,” read the compatibility rationale. Focus on the distinction between a lint rule and a formatter rule.

Replace the generated eslint.config.mjs with the following configuration, or carefully add the prettier import and entry if your scaffold has a newer equivalent structure:

import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import prettier from "eslint-config-prettier/flat";

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  prettier,
  globalIgnores([
    ".next/**",
    "out/**",
    "build/**",
    "next-env.d.ts",
  ]),
]);

export default eslintConfig;

This configuration has three meaningful layers:

  • core-web-vitals applies Next.js, React, React Hooks, and stricter performance-related Next.js rules.
  • typescript adds TypeScript-specific linting rules.
  • prettier disables conflicting formatting rules, allowing Prettier to be the single formatter.

Next, create .prettierrc.json in the repository root:

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "tabWidth": 2
}

These are conventions, not universal truths. What matters most is that the repository has one explicit, automated formatting style. Do not add lots of subjective ESLint rules such as mandatory quote preferences when Prettier already handles them.

Create .prettierignore as well:

.next
node_modules
coverage

Now update the scripts field in package.json. Preserve the scaffold’s existing scripts and add the quality commands below:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "typecheck": "tsc --noEmit",
    "format": "prettier . --write",
    "format:check": "prettier . --check"
  }
}

Run the baseline checks now:

npm run format
npm run lint
npm run typecheck
npm run build

A successful npm run build is useful, but remember that modern Next.js no longer automatically runs ESLint during the build. That is why npm run lint remains an explicit project command and later belongs in continuous integration.

For everyday development:

  • Use npm run format when you want Prettier to rewrite files.
  • Use npm run format:check when you only want a pass/fail result.
  • Use npm run lint:fix only after understanding what it changes; review the diff afterward.
  • Run npm run lint, npm run typecheck, and npm run build before opening a pull request.

If you use VS Code, enabling Format on Save with the Prettier extension is convenient. It is not, however, a substitute for the repository scripts: a recruiter, collaborator, CI service, or future machine must be able to verify quality without relying on your local editor settings.


Establish a branching workflow that fits a portfolio project

Your project is initially a one-person repository, but use the same basic discipline expected in a team:

  • main represents the best known stable version.
  • Each focused backlog item gets its own short-lived branch.
  • Every completed change is reviewed in a pull request, even when you are the only reviewer.
  • Only a checked, understandable change is merged into main.
A feature branch is created from `main`, receives focused commits, is discussed through a pull request, and is merged back into `main` after review. This is the workflow to use for each meaningful backlog item in the job-application workspace.

The image shows the underlying idea: work may move independently on a feature branch, while main remains stable. A pull request is not merely a button used to merge code. It creates a review record containing the intent of the change, screenshots or verification notes, discussion, and the status of quality checks.

Create the initial baseline

create-next-app often initializes Git automatically. Check before doing anything else:

git status
git branch --show-current

If Git has not been initialized, run:

git init
git branch -M main

Add your planning material from the previous lesson so the repository documents the reason behind future changes:

docs/
  backlog.md
  mvp-scope.md

The first commit is the one reasonable exception to the “never commit directly to main” rule: it establishes the project baseline.

git add .
git commit -m "chore: initialize job application workspace"

Create an empty GitHub repository named job-application-workspace. Do not ask GitHub to generate a README, license, or .gitignore, because those files already exist locally and would create an unnecessary first-history conflict.

Connect and push your baseline. Substitute your own GitHub username:

git remote add origin https://github.com/YOUR_USERNAME/job-application-workspace.git
git push -u origin main

Before proceeding, confirm that the GitHub repository shows:

  • the initial commit;
  • your README.md if the scaffold generated one;
  • docs/backlog.md and docs/mvp-scope.md;
  • no node_modules, .next, .env, or .env.local files.

Never commit credentials, API tokens, private keys, or local environment files. The application will use environment variables later, but the repository must be safe before deployment is introduced.

Use predictable branch and commit names

Choose branch names that reveal both the kind of work and its intent:

Change typeBranch example
New user-facing capabilityfeat/pbi-03-create-application
Bug repairfix/invalid-date-feedback
Documentationdocs/update-mvp-scope
Tooling or maintenancechore/repository-quality-baseline

Use concise commit messages in a similar style:

feat(applications): add application form fields
fix(validation): announce required field errors
docs(backlog): clarify persistence scope
chore: configure prettier checks

The prefix makes scanning history faster; the rest describes a completed change in imperative language. Avoid vague messages such as changes, fix stuff, final update, or working.

The workflow for every backlog item

For example, when you begin the first technical backlog item, use this sequence:

  1. Start from an up-to-date main branch.

    git switch main
    git pull --ff-only origin main
    
  2. Create one focused branch.

    git switch -c docs/pbi-01-product-decisions
    
  3. Make only the changes needed for that backlog item. Keep unrelated cleanup for a separate branch.

  4. Inspect what changed.

    git status
    git diff
    
  5. Run the repository quality checks.

    npm run format:check
    npm run lint
    npm run typecheck
    npm run build
    
  6. Stage and commit the verified change.

    git add docs/
    git commit -m "docs(backlog): define application field decisions"
    
  7. Push the branch and open a pull request.

    git push -u origin docs/pbi-01-product-decisions
    
  8. Review the pull request before merging. Confirm that its description states:

    • the backlog item or user story addressed;
    • the user-visible or technical outcome;
    • how you verified it;
    • screenshots when the change affects the interface.
  9. Use Squash and merge for this project. It keeps main history concise: one pull request becomes one purposeful commit.

  10. Delete the merged remote branch, then update your local main before starting the next item.

git switch main
git pull --ff-only origin main
git branch -d docs/pbi-01-product-decisions

For now, this workflow uses manual quality checks. Do not add Husky, commit-message enforcement, lint-staged, and multiple third-party plugins merely to make the setup look advanced. Those tools can be valuable once tests and CI exist, but a consistent manual checklist that you actually follow is stronger than complicated automation you do not understand. Later in the course, you will add automated checks before deployment.


Your repository-quality checklist

Before treating this lesson as complete, verify the following in the actual repository:

  • node --version meets the current Next.js requirement.
  • The project starts with npm run dev.
  • The project uses TypeScript, App Router, Tailwind CSS, and the @/* alias.
  • npm run format completes successfully.
  • npm run format:check completes successfully immediately afterward.
  • npm run lint completes successfully.
  • npm run typecheck completes successfully.
  • npm run build completes successfully.
  • .prettierrc.json, .prettierignore, and the final eslint.config.mjs are committed.
  • docs/mvp-scope.md and docs/backlog.md are in the repository.
  • The baseline is pushed to GitHub on main.
  • You can create a branch, push it, and open a pull request.
  • Your GitHub repository contains no secrets or generated dependency folders.

Keep the README.md minimal for now if you wish: project name, one-sentence purpose, local setup commands, and quality commands are enough. You will write the recruiter-friendly final README after the application is built, tested, and deployed.


Key takeaways

You now have a stable engineering foundation for the job-application workspace:

  • create-next-app gives you a modern Next.js, TypeScript, App Router baseline with less configuration risk.
  • Prettier handles formatting, while ESLint and TypeScript handle different forms of code correctness.
  • Explicit scripts make quality checks reproducible beyond your local editor.
  • main should remain stable; each backlog item belongs on one focused branch.
  • Pull requests create useful portfolio evidence when they explain intent, verification, and visible results.
  • A simple workflow followed consistently is better than prematurely complex Git automation.

Next, you will build and deploy the first responsive application shell. That will turn this well-configured repository into the first public preview of your portfolio project.

Can't find a good explanation? Sign up and we'll make it for you

Sign up