Set up a React and TypeScript project with Vite
Outcome
Section titled “Outcome”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.
Why this matters
Section titled “Why this matters”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.
What is new and what is reused
Section titled “What is new and what is reused”- 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.
Required result
Section titled “Required result”You have completed the lesson when:
- a folder named
m3-workflow-labcontains an npm project created from Vite’sreact-tstemplate; npm run devserves the project and saved source changes appear in the browser;package.jsoncontains an explicittypecheckscript that runstsc -b;- a deliberate unsupported status makes
npm run typecheckfail even if the development page still renders; - the restored source passes
npm run typecheckandnpm run lint; src/App.tsxrenders the typed work item through semantic HTML;src/main.tsximportsstyles.scss;- Sass variables and a mixin produce the required page styles;
npm run buildcreates a freshdistfolder from the valid source;npm run previewserves 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:
node --versionnpm --versionThe 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:
Get-LocationIf you use Command Prompt, run:
cdThe path must identify the parent folder where m3-workflow-lab should be created. It must not identify an existing project repository.
If it does not work
Section titled “If it does not work”- If
nodeornpmis 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.ps1cannot run because scripts are disabled, usenpm.cmdin 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
cdwith the parent-folder path and run the location check again.
Create the Vite project
Section titled “Create the Vite project”Run this command in the confirmed parent folder:
npm create vite@latest m3-workflow-lab -- --template react-tsRead the command from left to right:
npm createruns a project-creation package.vite@latestrequests the current stablecreate-vitepackage for this new project.m3-workflow-labbecomes the new folder and npm package name.- the first
--passes the remaining options through npm to the creation tool; --template react-tsselects 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:
cd m3-workflow-labnpm installnpm 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.
Identify the generated responsibilities
Section titled “Identify the generated responsibilities”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.
Separate the dependency records
Section titled “Separate the dependency records”| 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.
Run the development server
Section titled “Run the development server”From the folder that contains package.json, run:
npm run devThe 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.htmland 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.
Use two named terminal roles
Section titled “Use two named terminal roles”Keep the project state visible:
- Terminal 1 — development server: runs
npm run devand remains occupied. - Terminal 2 — checks: opens in the same
m3-workflow-labfolder 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.
First browser check
Section titled “First browser check”Confirm all of these results before you edit generated source:
- The generated React page is visible.
- Activating its generated control changes its displayed value, if the current template includes that control.
- The browser Console has no red application error.
- Terminal 1 has no unresolved transformation error.
If it does not work
Section titled “If it does not work”- If npm cannot find
package.json, useGet-Locationorcdto confirm that the terminal is insidem3-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 installin 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.
Make each tool command explicit
Section titled “Make each tool command explicit”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:
"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:
npm run typecheckThe 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 transformation is not type checking
Section titled “Vite transformation is not type checking”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 ANDTypeScript check failsThe rendering result proves that Vite transformed and served the current path. It does not prove that every TypeScript relationship is valid.
Run one deliberate disagreement
Section titled “Run one deliberate disagreement”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 the generated demonstration
Section titled “Replace the generated demonstration”Replace all content in src/App.tsx with this minimal application component:
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:
WorkStatuslimits the stored state;WorkItemdefines the object shape;firstItemmust 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:
- Return to the development page. Vite can still transform and render the string because the JavaScript runtime can use it.
- In Terminal 2, run
npm run typecheck. - Confirm that TypeScript reports that
"blocked"is not assignable toWorkStatus.
The browser result and checker result do not contradict each other. They answer different questions.
Restore status: "planned" and run the check again:
npm run typecheckThe command must exit without an error.
If it does not work
Section titled “If it does not work”- If the page still shows the generated demonstration, confirm that you edited and saved
src/App.tsxin the running project. - If an import error names
App.css, confirm that the replacement removed the generatedimport './App.css'line. - If
typecheckis not an npm script, inspect thescriptsobject and confirm the exact spelling and JSON commas. - If the restored status still fails, read the first type error and confirm that every
WorkItemproperty 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.
Add Sass as a bounded preprocessor
Section titled “Add Sass as a bounded preprocessor”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:
npm install --save-dev sassThis 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.
Rename the global stylesheet
Section titled “Rename the global stylesheet”In the VS Code Explorer:
- Rename
src/index.csstosrc/styles.scss. - Open
src/main.tsx. - Change the stylesheet import from
./index.cssto./styles.scss. - Confirm that
src/App.tsxdoes not importApp.css. - Delete the now-unused
src/App.cssfile 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.
Add the Sass source
Section titled “Add the Sass source”Replace all content in src/styles.scss with this code:
$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-surfacemixin 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.
Inspect the transformation boundary
Section titled “Inspect the transformation boundary”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 it does not work
Section titled “If it does not work”- If Vite reports that it cannot load Sass, confirm that
sassappears indevDependenciesand thatnpm installcompleted in this project. - If Vite cannot find
index.css, update the import insrc/main.tsxto the exact./styles.scssfile name. - If Vite cannot find
App.cssor a generated image, remove the unused import fromApp.tsxor 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.tsximports 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.
Verify the production path
Section titled “Verify the production path”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:
npm run typechecknpm run lintnpm run buildStop 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.
Preview the production output
Section titled “Preview the production output”Stop the development server in Terminal 1 with Ctrl+C. Then run this command:
npm run previewOpen the exact URL that Vite reports. Verify:
- The heading and status match the development result.
- The compiled panel styles are present.
- The page has no horizontal scrolling at 320 CSS pixels.
- The browser Console has no red application error.
- 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.
Read the complete pipeline
Section titled “Read the complete pipeline”source editing -> npm run typecheck -> npm run lint -> npm run build -> dist -> npm run preview -> browser verificationEach 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.
- Run node --version and confirm that the project uses the course Node.js LTS line.
- Confirm that package.json declares the project scripts and direct packages while package-lock.json records the resolved dependency graph.
- Confirm that node_modules and dist are generated and excluded by the generated .gitignore.
- Start npm run dev, open the reported URL, and confirm that the typed work-item page renders without a Console error.
- Explain why the development page can render while npm run typecheck reports an unsupported WorkStatus.
- Restore status planned and confirm that npm run typecheck passes.
- Confirm that src/main.tsx imports styles.scss and no source file imports the deleted generated CSS or image assets.
- Inspect the browser CSS and confirm that Sass variables and the mixin do not remain as browser syntax.
- Run npm run lint and resolve each configured-rule failure in project source.
- Run npm run build and confirm that dist contains generated index.html, JavaScript, and CSS assets.
- Run npm run preview and verify the heading, status, styles, narrow viewport, Network requests, and Console result.
- 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.
Reference documentation
Section titled “Reference documentation”- 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.
Next step or safe stopping point
Section titled “Next step or safe stopping point”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.