Skip to content

Set up a React and TypeScript project with Vite

You will create a local React and TypeScript workspace with Vite, add an explicit type-check command, replace the generated demonstration with a small typed page, compile Sass to CSS, and verify both the development and production paths.

A modern web project uses more than the browser. The development server, type checker, preprocessor, linter, and production builder answer different questions. When those responsibilities remain separate, you can identify which tool reported a problem and which result the tool actually verified.

Vite also generates a usable baseline. That generated code is boilerplate: repeated setup that gives a project a known starting state. Boilerplate saves setup time, but you still need to inspect, adapt, and maintain it.

What you will practice

  • Create a React and TypeScript project from the official Vite template.
  • Explain what package.json, package-lock.json, node_modules, tsconfig files, and vite.config.ts control.
  • Distinguish the development server, TypeScript check, linter, production build, and production preview.
  • Explain why a page can render through Vite while a separate TypeScript check still fails.
  • Add Sass without a Vite-specific Sass plugin and confirm that the browser receives CSS.
  • Use one repeatable command sequence to verify the workspace before a Git checkpoint.
  • New: Node.js as the local tool runtime, npm packages and scripts, Vite project scaffolding, the React TypeScript template, development serving, hot module replacement, project configuration, production bundling, generated output, linting, Sass preprocessing, and the difference between build-time and runtime responsibilities.
  • Reused: Terminal navigation, project folders, HTML entry points, JavaScript modules, TypeScript models, browser developer tools, CSS, responsive layout, accessibility checks, deliberate errors, and the JavaScript-to-TypeScript distinction from the previous lesson.

Starting point

Before you start

  • The completed Move from JavaScript to TypeScript lesson, including WorkStatus, WorkItem, static checks, and runtime checks.
  • VS Code, a current browser, Node.js 24 LTS, npm, and permission to download packages from the npm registry.
  • A parent folder for Module 3.1 work. Do not create the project inside another Git repository or inside this teaching-site repository.
  • No global Vite or Sass installation is required.
Current state
The TypeScript model runs in the browser Playground. No local React, Vite, or Sass workspace exists yet.
First action
Open a terminal in the parent folder for your Module 3.1 work and run node --version.
First checkpoint
The terminal reports a Node.js 24.x version and an npm version, and the terminal is still in the intended parent folder.
Help trigger
Use the nearest recovery note or ask for help if Node or npm is unavailable, package installation reports a network or permission failure, the terminal cannot find package.json, the page and type checker disagree in an unexplained way, or a required command still fails after you restore the matching lesson code.

You have completed the lesson when:

  • a folder named m3-workflow-lab contains an npm project created from Vite’s react-ts template;
  • npm run dev serves the project and saved source changes appear in the browser;
  • package.json contains an explicit typecheck script that runs tsc -b;
  • a deliberate unsupported status makes npm run typecheck fail even if the development page still renders;
  • the restored source passes npm run typecheck and npm run lint;
  • src/App.tsx renders the typed work item through semantic HTML;
  • src/main.tsx imports styles.scss;
  • Sass variables and a mixin produce the required page styles;
  • npm run build creates a fresh dist folder from the valid source;
  • npm run preview serves that production build for a local check; and
  • the final source passes the complete verification sequence without an unresolved browser Console error.

Confirm the local runtime before project creation

Section titled “Confirm the local runtime before project creation”

Node.js runs the project tools on your computer. The browser remains the runtime for the React client. npm installs project packages and runs named commands from package.json.

This lesson uses Node.js 24 LTS. LTS means long-term support. The course uses an LTS release because maintained versions continue to receive fixes and current tools support them.

From the parent folder for your Module 3.1 work, run:

Check the local tool versions
node --version
npm --version

The Node.js result must start with v24. for this course setup. The npm version can differ because npm receives its own updates.

Also confirm the current folder:

PowerShell — show the current folder
Get-Location

If you use Command Prompt, run:

Command Prompt — show the current folder
cd

The path must identify the parent folder where m3-workflow-lab should be created. It must not identify an existing project repository.

  • If node or npm is not recognized, stop here and use the school’s Node.js setup route. Do not install a second copy from an unrelated download site.
  • If the Node.js major version is older than the course setup, update through the approved installation method before you scaffold the project.
  • If PowerShell reports that npm.ps1 cannot run because scripts are disabled, use npm.cmd in the same command or open Command Prompt. Do not change a managed computer’s execution policy for this lesson.
  • If the current path is wrong, use cd with the parent-folder path and run the location check again.

Run this command in the confirmed parent folder:

Create the React and TypeScript workspace
npm create vite@latest m3-workflow-lab -- --template react-ts

Read the command from left to right:

  • npm create runs a project-creation package.
  • vite@latest requests the current stable create-vite package for this new project.
  • m3-workflow-lab becomes the new folder and npm package name.
  • the first -- passes the remaining options through npm to the creation tool;
  • --template react-ts selects the official React and TypeScript template.

npm can ask permission to download create-vite when it is not already cached. Confirm that prompt only when it names the expected package.

When creation finishes, move into the project and install its declared packages:

Enter the project and install dependencies
cd m3-workflow-lab
npm install

npm install reads package.json, resolves versions, downloads packages into node_modules, and records the exact resolved dependency graph in package-lock.json.

Do not install Vite globally. The project owns its Vite version through devDependencies, and npm scripts use that project-local executable.

The exact demonstration assets can change when the official template changes. These files carry the stable responsibilities that matter in this lesson:

  • Directorym3-workflow-lab/
    • Directorypublic/ — Static files copied without source transformation
      • …
    • Directorysrc/
      • Directoryassets/ — Generated demonstration assets; remove unused files later
        • …
      • App.css — Generated component styles; remove after replacement
      • App.tsx — Current React application component
      • index.css — Current global stylesheet
      • main.tsx — Browser entry module that renders App
    • .gitignore — Generated-file exclusions for the later Git repository
    • .oxlintrc.json — Linter configuration when supplied by the current template
    • index.html — Vite HTML entry point and root element
    • package-lock.json — Exact npm dependency resolution
    • package.json — Package metadata, scripts, and direct dependencies
    • tsconfig.app.json — TypeScript rules for browser application source
    • tsconfig.json — References the TypeScript project configurations
    • tsconfig.node.json — TypeScript rules for tool configuration
    • vite.config.ts — Vite configuration and React plugin

node_modules also exists after installation. It is generated and can contain many files, so the tree above omits it. Do not edit files inside node_modules. Change package.json or use an npm install command, then let npm manage the generated dependency folder.

Item Source of truth Commit later? Recovery route
package.json Direct dependencies and project scripts chosen by the team Yes Edit deliberately or use an npm command
package-lock.json Exact versions resolved by npm Yes Run npm install after a deliberate dependency change
node_modules Installed local package files No Delete only when recovery requires it, then run npm install
dist Generated production output No for this course project Run npm run build again

Do not run an automatic dependency-fix command only because npm prints an audit summary. Record the message and review the proposed changes before changing versions. A setup lesson needs a reproducible baseline, not an unrelated dependency migration.

From the folder that contains package.json, run:

Start the Vite development server
npm run dev

The terminal prints a local URL. Open that exact URL in the browser. Keep the terminal process running.

The development server performs several jobs:

  • serves index.html and requested source modules;
  • transforms TypeScript and TSX into JavaScript the browser can run;
  • connects the React plugin;
  • processes imported styles;
  • reports transformation errors; and
  • updates changed modules during development.

The update behavior is called hot module replacement (HMR). A supported module can update without a complete page reload. HMR improves the edit-and-check loop, but it is not production output.

Keep the project state visible:

  • Terminal 1 — development server: runs npm run dev and remains occupied.
  • Terminal 2 — checks: opens in the same m3-workflow-lab folder and runs type checks, linting, and builds.

Do not start a second development server because Terminal 1 looks occupied. That occupied terminal is evidence that the first server process is active.

Confirm all of these results before you edit generated source:

  1. The generated React page is visible.
  2. Activating its generated control changes its displayed value, if the current template includes that control.
  3. The browser Console has no red application error.
  4. Terminal 1 has no unresolved transformation error.
  • If npm cannot find package.json, use Get-Location or cd to confirm that the terminal is inside m3-workflow-lab.
  • If the port is already in use, open the new URL that Vite reports. Do not assume a fixed port.
  • If the browser cannot connect, confirm that Terminal 1 still shows an active server and that you opened the complete reported URL.
  • If package imports cannot be resolved, stop the server with Ctrl+C, run npm install in the project root, and start the server again.
  • If a transformation error names a file and line, correct the first reported source error before changing configuration.

Checkpoint: The generated development workspace runs

What now works
The official React and TypeScript template is installed, Terminal 1 serves it through Vite, the generated interaction works when present, and the browser Console has no application error.
Files changed
package.json, package-lock.json, src/App.tsx, src/main.tsx, vite.config.ts, tsconfig.json
What remains
Expose type checking as a separate command and replace the demonstration with the course data model.
Next action
Leave Terminal 1 running, open Terminal 2 in the project root, and inspect the scripts property in package.json.
If it does not work
Confirm the project path, installed dependencies, active server URL, and first transformation or Console error in that order.

Open package.json and find its scripts property. The current React TypeScript template supplies commands with these responsibilities:

npm command Underlying job Evidence it can provide
npm run dev Start Vite’s development server The source can be transformed and served for development
npm run build Run the template’s TypeScript build check, then create a Vite production build Valid source can produce deployable static assets
npm run lint Run the configured linter Source follows the configured static rules
npm run preview Serve the built dist output locally The current production output can be inspected locally

Add one explicit script so a type check can run without creating a production bundle. Insert this property after dev and before build inside the existing scripts object:

package.json — add inside scripts
"typecheck": "tsc -b",

Keep the existing dev, build, lint, and preview properties. Check the comma before and after the inserted line. JSON does not permit a trailing comma after the final property.

Run the new command in Terminal 2:

Check the complete TypeScript project
npm run typecheck

The command uses the project’s local TypeScript compiler. -b tells TypeScript to check the referenced application and tool-configuration projects. The template configurations use noEmit, so this check does not create application JavaScript.

Vite transforms TypeScript and TSX quickly enough to serve modules during development. It does not check the complete project type graph as part of that transformation.

This separation creates an important possible state:

development page renders
AND
TypeScript check fails

The rendering result proves that Vite transformed and served the current path. It does not prove that every TypeScript relationship is valid.

You will replace App.tsx in the next section. After that valid replacement runs, you will change one supported status to an unsupported status and compare the development page with npm run typecheck.

Do not keep a deliberate type error in the final workspace.

Replace all content in src/App.tsx with this minimal application component:

m3-workflow-lab/src/App.tsx
type WorkStatus = "planned" | "active" | "done";
type WorkItem = {
id: number;
title: string;
status: WorkStatus;
labels: string[];
};
const firstItem: WorkItem = {
id: 1,
title: "Create accessible navigation",
status: "planned",
labels: ["frontend", "accessibility"],
};
function App() {
return (
<main className="app-shell">
<p className="eyebrow">Level 3.1 workflow lab</p>
<h1>{firstItem.title}</h1>
<p>
Status: <strong>{firstItem.status}</strong>
</p>
<p>Vite, React, and TypeScript are connected.</p>
</main>
);
}
export default App;

This lesson uses one generated React component without explaining the complete component model. The next React lesson teaches components, props, and rendering in detail.

For now, identify the reused TypeScript evidence:

  • WorkStatus limits the stored state;
  • WorkItem defines the object shape;
  • firstItem must satisfy the model; and
  • the JSX expressions inside braces read trusted properties from that typed object.

Save App.tsx and return to the browser. The page must show:

  • Level 3.1 workflow lab;
  • Create accessible navigation as the page heading;
  • Status: planned; and
  • Vite, React, and TypeScript are connected.

The generated styling can remain temporarily. The next checkpoint replaces it.

Compare a deliberate type failure with the browser

Section titled “Compare a deliberate type failure with the browser”

In firstItem, change status from "planned" to "blocked".

Observe both surfaces:

  1. Return to the development page. Vite can still transform and render the string because the JavaScript runtime can use it.
  2. In Terminal 2, run npm run typecheck.
  3. Confirm that TypeScript reports that "blocked" is not assignable to WorkStatus.

The browser result and checker result do not contradict each other. They answer different questions.

Restore status: "planned" and run the check again:

Restore a clean type-check result
npm run typecheck

The command must exit without an error.

  • If the page still shows the generated demonstration, confirm that you edited and saved src/App.tsx in the running project.
  • If an import error names App.css, confirm that the replacement removed the generated import './App.css' line.
  • If typecheck is not an npm script, inspect the scripts object and confirm the exact spelling and JSON commas.
  • If the restored status still fails, read the first type error and confirm that every WorkItem property matches its declared type.
  • If the page does not update after saving, inspect Terminal 1 for a transformation error and reload the reported URL once.

Checkpoint: Development transformation and type checking are separate

What now works
The typed work-item page renders through Vite, the unsupported status produces a TypeScript failure, and the restored planned status passes the explicit typecheck command.
Files changed
package.json, src/App.tsx
What remains
Add the required Sass preprocessor, replace generated global styles, and verify the production path.
Next action
In Terminal 2, install Sass as a project development dependency.
If it does not work
Restore the complete App.tsx example, confirm the browser page first, then run npm run typecheck and repair its first error.

Sass is a stylesheet language that compiles to CSS. The browser does not execute Sass variables or mixins. Vite runs Sass during development and production builds, then supplies CSS to the browser.

Install Dart Sass as a development dependency:

Install the selected Sass implementation
npm install --save-dev sass

This course selects the sass package for a predictable cross-platform classroom setup. Vite supports Sass directly after the Sass implementation is installed. Do not add a Vite-specific Sass plugin.

In the VS Code Explorer:

  1. Rename src/index.css to src/styles.scss.
  2. Open src/main.tsx.
  3. Change the stylesheet import from ./index.css to ./styles.scss.
  4. Confirm that src/App.tsx does not import App.css.
  5. Delete the now-unused src/App.css file and generated assets that no remaining source file imports.

Do not delete main.tsx, App.tsx, or an asset that still appears in an import. Let the first unresolved-import error identify a missed relationship if the template differs from the file list.

Replace all content in src/styles.scss with this code:

m3-workflow-lab/src/styles.scss
$space-unit: 0.25rem;
$space-4: $space-unit * 4;
$space-8: $space-unit * 8;
$content-width: 48rem;
$panel-radius: 0.75rem;
@mixin panel-surface {
border: 1px solid #b8c8bf;
border-radius: $panel-radius;
background: #ffffff;
box-shadow: 0 0.25rem 1rem rgb(23 33 28 / 10%);
}
:root {
font-family: Inter, system-ui, sans-serif;
color: #17211c;
background: #f2f6f3;
font-synthesis: none;
text-rendering: optimizeLegibility;
}
* {
box-sizing: border-box;
}
body {
min-width: 20rem;
min-height: 100vh;
margin: 0;
}
.app-shell {
@include panel-surface;
width: min(calc(100% - 2rem), $content-width);
margin: $space-8 auto;
padding: $space-8;
}
.eyebrow {
margin-block: 0 $space-4;
color: #1c6243;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
h1 {
overflow-wrap: anywhere;
margin-block: 0 $space-4;
}
@media (max-width: 30rem) {
.app-shell {
margin-block: $space-4;
padding: $space-4;
}
}

The source uses two Sass features:

  • variables store build-time spacing, width, and radius values; and
  • the panel-surface mixin groups declarations that a selector can include.

The source also keeps accessibility and responsive correctness visible:

  • foreground and background colors have strong contrast;
  • the page retains a 20-rem minimum layout baseline;
  • the content width accounts for narrow viewports;
  • long heading text can wrap; and
  • the media query reduces spacing without hiding content.

After you save the file, the browser must show the work item inside the bordered panel. Open developer tools and inspect .app-shell.

The Styles panel shows CSS declarations. It does not show $space-8, $content-width, or @include panel-surface as browser CSS because Sass resolves them before the browser receives the stylesheet.

Compare the build-time tools:

Source Build-time responsibility Browser receives
TypeScript and TSX Vite transforms syntax; tsc separately checks types JavaScript
Sass Sass resolves variables, mixins, and other Sass syntax CSS
React JSX The React Vite plugin transforms JSX for the React runtime JavaScript that creates React elements

Use Sass only when it makes the source easier to maintain. Modern CSS already has custom properties, nesting, calculations, media queries, and other strong features. This course uses a small Sass boundary to meet the preprocessor learning goal without moving every CSS responsibility into Sass.

  • If Vite reports that it cannot load Sass, confirm that sass appears in devDependencies and that npm install completed in this project.
  • If Vite cannot find index.css, update the import in src/main.tsx to the exact ./styles.scss file name.
  • If Vite cannot find App.css or a generated image, remove the unused import from App.tsx or restore the still-used asset.
  • If Sass reports an undefined variable or mixin, check the spelling and confirm that its declaration appears before its use.
  • If the panel is unstyled, inspect the browser Network and Console panels, then confirm that main.tsx imports the Sass file.

Checkpoint: Sass produces browser CSS through Vite

What now works
The React page imports styles.scss, the browser displays the responsive panel, and developer tools show compiled CSS rather than Sass variables or mixin syntax.
Files changed
package.json, package-lock.json, src/main.tsx, src/styles.scss, src/App.tsx
What remains
Run the linter, create and inspect the production build, and record the repeatable verification order.
Next action
Keep the valid browser page open and run npm run lint in Terminal 2.
If it does not work
Confirm the installed Sass dependency, exact stylesheet import, first transformation error, and compiled browser styles in that order.

The development server works from source and keeps development-only connections active. A production build creates optimized static assets in dist.

Run the required checks in Terminal 2:

Check source before the production build
npm run typecheck
npm run lint
npm run build

Stop at the first failed command. Fix that result, then restart the sequence from typecheck.

The current React TypeScript template’s build script runs the TypeScript build check before Vite’s production builder. Keeping the separate typecheck command still matters because you can run it without bundling, use it at a checkpoint, and identify its responsibility directly.

After a successful build, inspect the generated folder:

  • Directorydist/
    • Directoryassets/
      • index-[generated-hash].css
      • index-[generated-hash].js
    • index.html
    • other copied public assets when present

The generated hash can change when source content changes. Do not hard-code the generated file name in source or documentation.

Do not edit dist to fix the application. Change the source, rerun the checks, and rebuild. The next build can replace generated files.

Stop the development server in Terminal 1 with Ctrl+C. Then run this command:

Serve the current production build locally
npm run preview

Open the exact URL that Vite reports. Verify:

  1. The heading and status match the development result.
  2. The compiled panel styles are present.
  3. The page has no horizontal scrolling at 320 CSS pixels.
  4. The browser Console has no red application error.
  5. The Network panel loads generated files from dist/assets.

preview is a local verification server. It does not deploy the application and must not replace a production web server.

Stop the preview with Ctrl+C after the check. Restart npm run dev only when you continue source development.

source editing
-> npm run typecheck
-> npm run lint
-> npm run build
-> dist
-> npm run preview
-> browser verification

Each arrow represents a dependency. A later green result does not erase an earlier failed check. The course workflow stops at the first failure and restores a complete green sequence before a Git checkpoint.

Self-check

Complete these checks against the required result.

  1. Run node --version and confirm that the project uses the course Node.js LTS line.
  2. Confirm that package.json declares the project scripts and direct packages while package-lock.json records the resolved dependency graph.
  3. Confirm that node_modules and dist are generated and excluded by the generated .gitignore.
  4. Start npm run dev, open the reported URL, and confirm that the typed work-item page renders without a Console error.
  5. Explain why the development page can render while npm run typecheck reports an unsupported WorkStatus.
  6. Restore status planned and confirm that npm run typecheck passes.
  7. Confirm that src/main.tsx imports styles.scss and no source file imports the deleted generated CSS or image assets.
  8. Inspect the browser CSS and confirm that Sass variables and the mixin do not remain as browser syntax.
  9. Run npm run lint and resolve each configured-rule failure in project source.
  10. Run npm run build and confirm that dist contains generated index.html, JavaScript, and CSS assets.
  11. Run npm run preview and verify the heading, status, styles, narrow viewport, Network requests, and Console result.
  12. Stop the preview process and leave the valid source ready for the team-version-control lesson.

Checkpoint: The development and production workflows are both verified

What now works
The valid React and TypeScript source passes type checking and linting, Sass produces browser CSS, Vite creates dist, and the previewed production build passes the required browser checks.
Files changed
package.json, package-lock.json, src/App.tsx, src/main.tsx, src/styles.scss, index.html
What remains
Place the workspace under version control, connect a shared remote, and practice a team integration path.
Next action
Stop all local server processes, keep m3-workflow-lab, and open the team-version-control lesson.
If it does not work
Run typecheck, lint, build, and preview in order. Repair the first failed result before you use a later command as evidence.
  • Vite getting started documents the project templates, Node.js requirements, development server, build command, and preview command.
  • Vite features documents TypeScript transpilation without type checking and built-in Sass integration after a Sass implementation is installed.
  • Vite production builds explains the production build and generated assets.
  • Sass documentation defines Sass as a stylesheet language that compiles to CSS and documents variables, mixins, and modules.
  • Node.js releases identifies maintained LTS release lines.

The required lesson is complete when the restored source passes npm run typecheck, npm run lint, and npm run build, and the local production preview passes the browser checks.

Continue to Work with version control in a team to turn this local workspace into a reviewable shared repository.

If you stop here, leave this resume note: The Vite development page, TypeScript check, linter, Sass transformation, production build, and production preview all pass. Next, initialize the repository and practice a team branch, review, and integration path.