React Router works fine right up until someone hits refresh on /dashboard/settings or bookmarks a deep link, and IIS throws a 404 because there's no physical file at that path. This isn't a React problem — it's a static-file server being told to serve files that don't exist as files. Fixing it is a five-minute job once you know which module and which rewrite rule you need.
Getting the Build onto IIS
Run npm run build (Vite outputs to dist instead of build) and copy the contents of that folder — not the folder itself — into the site's physical path in IIS. Create or point a site/application at that folder, confirm index.html is listed under Default Document, and you'll get a working app for every route except the ones a user navigates to directly instead of through client-side routing. That's the gap the URL Rewrite module closes.
The web.config That Saves You From 404s
Install the URL Rewrite module from the IIS downloads page (it's not bundled by default), then drop a web.config in the site root with a rule that rewrites any request that isn't an existing file or directory back to index.html. React Router then picks up the original URL from the browser's address bar and renders the right route client-side — IIS never needs to know /dashboard/settings exists.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="React Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
<staticContent>
<remove fileExtension=".json" />
<mimeMap fileExtension=".json" mimeType="application/json" />
<remove fileExtension=".woff2" />
<mimeMap fileExtension=".woff2" mimeType="font/woff2" />
</staticContent>
</system.webServer>
</configuration>
- homepage in package.json (Create React App) must match the site's virtual path, or every asset URL breaks on a subfolder deployment
- A trailing-slash mismatch between the app's base path and the IIS application path causes relative assets to 404 silently
- Older IIS installs lack MIME mappings for .json, .woff2, and .mjs — add them explicitly in staticContent rather than assuming the platform default covers modern build output
- Adding a new mimeMap for an extension IIS already maps throws a duplicate-entry error — always remove before you add
MIME Types and Static Assets
Windows Server's IIS install can be a few years behind on its default MIME map, especially for .woff2 fonts and .json chunks that modern bundlers emit. If a font silently fails to load or a lazy-loaded route's JSON manifest 404s in the network tab while the file clearly exists on disk, it's almost always a missing mimeMap entry rather than a routing problem — check that before touching the rewrite rule again.
After editing web.config, recycle the app pool rather than assuming IIS picks it up live — rewrite rule changes usually apply immediately, but MIME map and other system.webServer changes sometimes need the worker process restarted to take effect.
Wrapping Up
Two things cover almost every React-on-IIS deployment: the rewrite rule that hands unmatched paths back to index.html, and correct MIME mappings for whatever your bundler emits. Get those in place and IIS treats the app like any other static site.