Vite Dev Server and HMR
The development server turns a saved source edit into browser feedback.
Three programs take part when you edit a component.
- The editor writes a changed file to disk.
- Vite detects the file and sends a module update to the browser.
- React Fast Refresh decides how React component code should be replaced.
The browser may update without reloading the document, but that outcome is conditional. It is not the same as React rendering after an application state update.
The Development Server Is Not a Production Server
Start the project.
npm run devVite opens an HTTP server for local development. It serves index.html, transforms requested source modules, resolves package imports, and maintains a connection used to announce updates.
During development, the browser can request source modules individually.
index.html
/src/main.jsx
/src/App.jsx
/src/ChapterCard.jsx
/src/index.cssThis is one reason startup can be quick. Vite does not have to emit the final production bundle before the browser can load the first page. It transforms modules as they are requested and pre-bundles selected dependencies for development efficiency.
Production uses another path. vite build creates optimized output ahead of deployment. The local dev server should not be exposed as the deployed application server.
vite --host 0.0.0.0 makes the development server reachable on the local network. It does not turn the server into a hardened production deployment.
Save One Component Edit
Begin with state that is easy to observe.
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((n) => n + 1)}>{count}</button>;
}Click until the button shows 3. Then change only its surrounding text.
return <button onClick={() => setCount((n) => n + 1)}>Count: {count}</button>;In a normal Fast Refresh update, the new text appears and the count remains 3.
This result contains two different kinds of continuity.
- Vite kept the current browser document alive while replacing an accepted module.
- React kept the compatible component instance and its local state.
Vite alone does not understand React Hook state. The React plugin supplies the Fast Refresh integration that lets React decide whether the component family remains compatible.
HMR and Fast Refresh Are Different Layers
Hot Module Replacement, or HMR, is Vite's system for replacing modules while an application is running. CSS can use HMR without React.
.counter {
color: #61dafb;
}Save a color change and Vite can update the stylesheet without recreating the page.
React Fast Refresh is specific to React component modules. It registers component exports, detects compatible edits, and asks React to refresh affected trees.
saved file
-> Vite finds importers
-> Vite sends HMR update
-> React plugin identifies component exports
-> Fast Refresh replaces compatible component code
-> React renders affected componentsAn application state update starts with a state setter. A Fast Refresh update starts with edited module code. Both can cause React to render, but their sources and preservation rules differ.
Why State Can Survive an Edit
React can preserve local state when the edited export still represents the same compatible component family and Hook order remains valid.
Change a label or add ordinary rendering logic and preservation often works.
const label = count === 1 ? "lesson" : "lessons";Change the Hook order and preservation would be unsafe.
function Counter({ detailed }) {
if (detailed) {
const [label, setLabel] = useState("Lessons");
}
const [count, setCount] = useState(0);
// ...
}This code also violates the Rules of Hooks and must be repaired. Fast Refresh does not make conditional Hooks acceptable.
State can reset after edits to a component's Hooks. Adding or removing a Hook may produce a refresh with fresh state so React can establish a valid Hook list.
That reset is a development event, not proof of production behavior. A deployed user is not editing component source.
Module Exports Affect Refresh Boundaries
Fast Refresh works best when a module exports React components. Consider a component module that also exports a value used by non-React code.
export const chapterLimit = 5;
export default function ChapterList() {
// ...
}An edit can affect importers that are outside the React refresh path. Depending on the import graph, Vite may need to invalidate more modules or reload the page.
Keep reusable constants in their own module when they have non-component consumers.
// chapter-config.js
export const chapterLimit = 5;// ChapterList.jsx
import { chapterLimit } from "./chapter-config.js";This is not a rule that every constant needs a file. It is a reason to keep component modules centered on component exports once values gain unrelated consumers.
If a file edit repeatedly causes a full reload, inspect what that module exports and which modules import it before blaming the component's state code.
A Full Reload Has Different Consequences
A full reload requests index.html again and starts the client application from the beginning.
browser discards current document
browser requests entry document
module graph loads again
createRoot runs again
component state initializes againIn-memory state resets unless it was saved somewhere outside the discarded page, such as a server, URL, or browser storage.
Some edits require this. Changing index.html, changing a module outside an accepted HMR path, or introducing an unrecoverable module error can lead to reload behavior.
You can always perform a manual reload. Doing so is a useful test after a sequence of Fast Refresh updates. It proves that the application works from a clean start instead of relying on preserved development state.
Treat Fast Refresh state preservation as editing assistance. Save behavior required by users in application data flows, not in the development server's memory.
Error Recovery Is Part of the Loop
Introduce a temporary syntax error.
return <button>{count</button>;Vite cannot transform the module, so it cannot send valid replacement code. The last valid module may remain behind the error overlay. After the syntax is repaired, Vite sends another update.
State may survive some error corrections and reset after others. The priority is a correct clean start, not preservation through every broken intermediate file.
The terminal and overlay report transform failures. A runtime exception after successful transformation appears in browser execution and React reporting. First determine whether Vite produced a valid update at all.
Dependency Optimization Is a Development Step
Browser source can import a package by name.
import { useState } from "react";Browsers do not resolve bare npm package names from node_modules on their own. Vite resolves those imports and can pre-bundle dependencies into browser-friendly modules. Vite 8 uses Rolldown for its production build and dependency optimization path.
When dependency metadata changes, Vite may optimize dependencies again. Installing a package while the dev server is already running can require a restart.
npm install date-fnsStop and restart Vite if the running server does not pick up the new graph.
npm run devDo not reach for cache deletion on every source error. Dependency optimization is relevant when installed packages or their resolution changed, not when an h1 tag is missing.
Environment Values Are Read at Server Startup
Vite exposes selected client environment values through import.meta.env. Client-exposed custom names use the VITE_ prefix by default.
VITE_API_ORIGIN=https://api.example.comconst apiOrigin = import.meta.env.VITE_API_ORIGIN;Vite replaces these values in client code. A value included in a client bundle is visible to users who can load that bundle.
Never place a private key, database password, or server credential in a VITE_ variable. The prefix selects data for client exposure. It does not make the value secret.
Environment files are loaded when Vite starts. Restart the development server after changing them so the new values are read consistently.
This project does not need an API credential. The example demonstrates why an environment change requires a server restart instead of an ordinary source refresh.
Network Access and Ports
The printed URL normally uses localhost and port 5173.
http://localhost:5173/If that port is occupied, Vite can choose another. Always open the URL printed by the current terminal rather than relying on an old bookmark.
The browser loads the page from the development server origin. Requests to another origin are subject to browser cross-origin rules. Vite's development proxy can forward selected paths, but a proxy is not needed for this local-data milestone.
Adding a proxy before an API exists would introduce configuration that has nothing to serve.
Test the Three Update Paths
Use the counter to observe three distinct outcomes.
- Change a CSS color. The style updates through HMR.
- Change component text. Fast Refresh normally preserves the count.
- Reload the browser. The component state starts from zero.
Then make sure the app behaves correctly after the third path. That is the clean-start behavior a reader or reviewer can reproduce.
What to Record in a Bug Report
When local update behavior fails, record concrete facts.
- Vite version from
npm ls vite - React plugin version from
npm ls @vitejs/plugin-react - changed file and its exports
- whether the terminal reported HMR, invalidation, or an error
- whether the browser refreshed the module or reloaded the document
- whether a manual reload repairs the visible problem
"Hot reload is broken" collapses several systems into one sentence. The facts above isolate the layer that behaved unexpectedly.
Source Notes
- Vite documentation, HMR API
- Vite documentation, Features
- Vite documentation, Environment Variables and Modes
- Vite release notes, Vite 8
- React documentation, Fast Refresh