Hot module reloading is a must-have for improving the devops experience. No more convoluted webpack configs and plugins, it all works (mostly) out of the box with lightning fast HMR.

The GitHub repo contains a template for using Vite as the base.

The basic principle stands the same. During development .NET will run the npm serve command and merge the outputs into the console window. This command runs Vite bound to a different port, proxying the /api calls to .NET. The difference with Vite is that it handles the entire frontend, from the index.html to all the public path content. For deployment, Vite will build to the wwwroot path, which Visual Studio will copy to the final deploy location.

Setup

Create your ASP.NET application. To load the index.html file that Vite creates, add the following to your Program.cs file.

1
app.UseDefaultFiles();

And add a test route to the Program.cs file.

1
app.MapGet(/api/test, () => "Hello World!");

And as we will no longer be using it, remove the Index.cshtml file from the Pages folder.

To setup the frontend, create a “client” folder and npm init to initialise npm, then install the following npm packages.

npm i vite vue @vitejs/plugin-vue

Add the following scripts to your npm package.

JavaScript
<span class="line"><span style="color: #6F42C1">u0022scriptsu0022</span><span style="color: #24292E">: {n  </span><span style="color: #6F42C1">u0022serveu0022</span><span style="color: #24292E">: u0022viteu0022,n  </span><span style="color: #6F42C1">u0022buildu0022</span><span style="color: #24292E">: u0022vite buildu0022n}</span></span>

Create a vite.config.js file next to the package.json file.

JavaScript

<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> { defineConfig } </span><span style="color: #D73A49">from</span><span style="color: #24292E"> </span><span style="color: #032F62">'vite'</span></span>
<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> vue </span><span style="color: #D73A49">from</span><span style="color: #24292E"> </span><span style="color: #032F62">'@vitejs/plugin-vue'</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D73A49">export</span><span style="color: #24292E"> </span><span style="color: #D73A49">default</span><span style="color: #24292E"> </span><span style="color: #6F42C1">defineConfig</span><span style="color: #24292E">(</span></span>
<span class="line"><span style="color: #24292E">  plugins: [</span><span style="color: #6F42C1">vue</span><span style="color: #24292E">()],</span></span>
<span class="line"><span style="color: #24292E">  root: </span><span style="color: #032F62">'./src'</span><span style="color: #24292E">,</span></span>
<span class="line"><span style="color: #24292E">  server: {</span></span>
<span class="line"><span style="color: #24292E">    proxy: {</span></span>
<span class="line"><span style="color: #24292E">      </span><span style="color: #032F62">'/api'</span><span style="color: #24292E">: {</span></span>
<span class="line"><span style="color: #24292E">        target: </span><span style="color: #032F62">'http://localhost:5207'</span><span style="color: #24292E"> </span><span style="color: #6A737D">// Proxies to ASP.NET</span></span>
<span class="line"><span style="color: #24292E">      }</span></span>
<span class="line"><span style="color: #24292E">    },</span></span>
<span class="line"><span style="color: #24292E">  },</span></span>
<span class="line"><span style="color: #24292E">  build: {</span></span>
<span class="line"><span style="color: #24292E">    outDir: </span><span style="color: #032F62">'../../wwwroot'</span><span style="color: #24292E">,</span></span>
<span class="line"><span style="color: #24292E">  }</span></span>
<span class="line"><span style="color: #24292E">})</span></span>

Create a src folder with an index.html. This will act as the entry point for Vite.

HTML
<span class="line"><span style="color: #24292E"><!</span><span style="color: #22863A">DOCTYPE</span><span style="color: #24292E"> </span><span style="color: #6F42C1">html</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E"><</span><span style="color: #22863A">html</span><span style="color: #24292E"> </span><span style="color: #6F42C1">lang</span><span style="color: #24292E">=</span><span style="color: #032F62">""</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">  <</span><span style="color: #22863A">head</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">meta</span><span style="color: #24292E"> </span><span style="color: #6F42C1">charset</span><span style="color: #24292E">=</span><span style="color: #032F62">"utf-8"</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">meta</span><span style="color: #24292E"> </span><span style="color: #6F42C1">http-equiv</span><span style="color: #24292E">=</span><span style="color: #032F62">"X-UA-Compatible"</span><span style="color: #24292E"> </span><span style="color: #6F42C1">content</span><span style="color: #24292E">=</span><span style="color: #032F62">"IE=edge"</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">meta</span><span style="color: #24292E"> </span><span style="color: #6F42C1">name</span><span style="color: #24292E">=</span><span style="color: #032F62">"viewport"</span><span style="color: #24292E"> </span><span style="color: #6F42C1">content</span><span style="color: #24292E">=</span><span style="color: #032F62">"width=device-width,initial-scale=1.0"</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">link</span><span style="color: #24292E"> </span><span style="color: #6F42C1">rel</span><span style="color: #24292E">=</span><span style="color: #032F62">"icon"</span><span style="color: #24292E"> </span><span style="color: #6F42C1">href</span><span style="color: #24292E">=</span><span style="color: #032F62">"favicon.ico"</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">script</span><span style="color: #24292E"> </span><span style="color: #6F42C1">type</span><span style="color: #24292E">=</span><span style="color: #032F62">"module"</span><span style="color: #24292E"> </span><span style="color: #6F42C1">src</span><span style="color: #24292E">=</span><span style="color: #032F62">"./main.js"</span><span style="color: #24292E">></</span><span style="color: #22863A">script</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    </</span><span style="color: #22863A">head</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">body</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">      <</span><span style="color: #22863A">div</span><span style="color: #24292E"> </span><span style="color: #6F42C1">id</span><span style="color: #24292E">=</span><span style="color: #032F62">"app"</span><span style="color: #24292E">></</span><span style="color: #22863A">div</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">  </</span><span style="color: #22863A">body</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E"></</span><span style="color: #22863A">html</span><span style="color: #24292E">></span></span>

And a main.js file next to it. If you’re using TypeScript, this can be .ts files (and change the src above to “main.ts”).

JavaScript
<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> { createApp } </span><span style="color: #D73A49">from</span><span style="color: #24292E"> </span><span style="color: #032F62">'vue'</span></span>
<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> App </span><span style="color: #D73A49">from</span><span style="color: #24292E"> </span><span style="color: #032F62">'./App.vue'</span></span>
<span class="line"><span style="color: #6F42C1">createApp</span><span style="color: #24292E">(App)</span></span>
<span class="line"><span style="color: #24292E">  .</span><span style="color: #6F42C1">mount</span><span style="color: #24292E">(</span><span style="color: #032F62">'#app'</span><span style="color: #24292E">)</span></span>

Add an App.vue file. The below is using Vue 3 setup composition syntax, but you can use the options API just as easily.

Vue
<span class="line"><span style="color: #24292E"><</span><span style="color: #22863A">style</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #6F42C1">#app</span><span style="color: #24292E"> {</span></span>
<span class="line"><span style="color: #24292E">  </span><span style="color: #005CC5">font-family</span><span style="color: #24292E">: Avenir, </span><span style="color: #005CC5">Helvetica</span><span style="color: #24292E">, </span><span style="color: #005CC5">Arial</span><span style="color: #24292E">, </span><span style="color: #005CC5">sans-serif</span><span style="color: #24292E">;</span></span>
<span class="line"><span style="color: #24292E">  </span><span style="color: #005CC5">-webkit-font-smoothing</span><span style="color: #24292E">: </span><span style="color: #005CC5">antialiased</span><span style="color: #24292E">;</span></span>
<span class="line"><span style="color: #24292E">  </span><span style="color: #005CC5">-moz-osx-font-smoothing</span><span style="color: #24292E">: </span><span style="color: #005CC5">grayscale</span><span style="color: #24292E">;</span></span>
<span class="line"><span style="color: #24292E">  </span><span style="color: #005CC5">text-align</span><span style="color: #24292E">: </span><span style="color: #005CC5">center</span><span style="color: #24292E">;</span></span>
<span class="line"><span style="color: #24292E">  </span><span style="color: #005CC5">color</span><span style="color: #24292E">: </span><span style="color: #005CC5">#2c3e50</span><span style="color: #24292E">;</span></span>
<span class="line"><span style="color: #24292E">  </span><span style="color: #005CC5">margin-top</span><span style="color: #24292E">: </span><span style="color: #005CC5">60</span><span style="color: #D73A49">px</span><span style="color: #24292E">;</span></span>
<span class="line"><span style="color: #24292E">}</span></span>
<span class="line"><span style="color: #24292E"></</span><span style="color: #22863A">style</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E"><</span><span style="color: #22863A">template</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">  <</span><span style="color: #22863A">div</span><span style="color: #24292E"> </span><span style="color: #6F42C1">id</span><span style="color: #24292E">=</span><span style="color: #032F62">"app"</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">p</span><span style="color: #24292E">>{{msg}}</</span><span style="color: #22863A">p</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">    <</span><span style="color: #22863A">div</span><span style="color: #24292E">><</span><span style="color: #22863A">a</span><span style="color: #24292E"> </span><span style="color: #6F42C1">href</span><span style="color: #24292E">=</span><span style="color: #032F62">"/api/test"</span><span style="color: #24292E">>Test Route</</span><span style="color: #22863A">a</span><span style="color: #24292E">></</span><span style="color: #22863A">div</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">  </</span><span style="color: #22863A">div</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E"></</span><span style="color: #22863A">template</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E"><</span><span style="color: #22863A">script</span><span style="color: #24292E"> </span><span style="color: #6F42C1">setup</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #D73A49">import</span><span style="color: #24292E"> { ref } </span><span style="color: #D73A49">from</span><span style="color: #24292E"> </span><span style="color: #032F62">'vue'</span></span>
<span class="line"><span style="color: #D73A49">const</span><span style="color: #24292E"> </span><span style="color: #005CC5">msg</span><span style="color: #24292E"> </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #6F42C1">ref</span><span style="color: #24292E">(</span><span style="color: #032F62">'Welcome to Your Vue.js App'</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E"></</span><span style="color: #22863A">script</span><span style="color: #24292E">></span></span>

Run your ASP.NET app, and npm run serve from within the “client” folder. Connect to http://localhost:3000 to view the Vite rendered frontend and visit the /api/test endpoint to view the .NET rendered backend.

Debugging

To automatically run the Vite server when the .NET project is debugged, at the following to your Program.cs file.

C#
<span class="line"><span style="color: #6A737D">// Run the serve command when running in development mode.</span></span>
<span class="line"><span style="color: #D73A49">if</span><span style="color: #24292E"> (app.Environment.</span><span style="color: #6F42C1">IsDevelopment</span><span style="color: #24292E">())</span></span>
<span class="line"><span style="color: #24292E">{</span></span>
<span class="line"><span style="color: #24292E">    Process.</span><span style="color: #6F42C1">Start</span><span style="color: #24292E">(</span><span style="color: #D73A49">new</span><span style="color: #24292E"> </span><span style="color: #6F42C1">ProcessStartInfo</span></span>
<span class="line"><span style="color: #24292E">    {</span></span>
<span class="line"><span style="color: #24292E">        FileName </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #032F62">"cmd"</span><span style="color: #24292E">,</span></span>
<span class="line"><span style="color: #24292E">        RedirectStandardInput </span><span style="color: #D73A49">=</span><span style="color: #24292E"> </span><span style="color: #005CC5">true</span><span style="color: #24292E">,</span></span>
<span class="line"><span style="color: #24292E">        WorkingDirectory </span><span style="color: #D73A49">=</span><span style="color: #24292E"> Path.</span><span style="color: #6F42C1">Combine</span><span style="color: #24292E">(Environment.CurrentDirectory, </span><span style="color: #032F62">"client"</span><span style="color: #24292E">)</span></span>
<span class="line"><span style="color: #24292E">    })</span><span style="color: #D73A49">!</span><span style="color: #24292E">.StandardInput.</span><span style="color: #6F42C1">WriteLine</span><span style="color: #24292E">(</span><span style="color: #032F62">"npm run serve"</span><span style="color: #24292E">);</span></span>
<span class="line"><span style="color: #24292E">}</span></span>

This will run the Vite serve command during development, with the output merged into the console. One thing to note is that the console will remain open when stopping the .NET server so keep an eye on your taskbar and shut down any orphaned Vite console windows.

Deployment

Vite will generate the frontend output into the wwwroot directory (deleting the contents if it already exists). Visual Studio can automate this building step with an msbuild config option.

In the Visual Studio file sidebar, double click on the project file to open it in a raw file view. Remove the following if it exists.

XML
<span class="line"><span style="color: #24292E"><</span><span style="color: #22863A">ItemGroup</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">  <</span><span style="color: #22863A">Content</span><span style="color: #24292E"> </span><span style="color: #6F42C1">Remove</span><span style="color: #24292E">=</span><span style="color: #032F62">"wwwrootindex.html"</span><span style="color: #24292E"> /></span></span>
<span class="line"><span style="color: #24292E"></</span><span style="color: #22863A">ItemGroup</span><span style="color: #24292E">></span></span>

And add the following in its place.

XML
<span class="line"><span style="color: #24292E"><</span><span style="color: #22863A">Target</span><span style="color: #24292E"> </span><span style="color: #6F42C1">Name</span><span style="color: #24292E">=</span><span style="color: #032F62">"PreBuild"</span><span style="color: #24292E"> </span><span style="color: #6F42C1">BeforeTargets</span><span style="color: #24292E">=</span><span style="color: #032F62">"PreBuildEvent"</span><span style="color: #24292E"> </span><span style="color: #6F42C1">Condition</span><span style="color: #24292E">=</span><span style="color: #032F62">"$(Configuration) == 'Release'"</span><span style="color: #24292E">></span></span>
<span class="line"><span style="color: #24292E">  <</span><span style="color: #22863A">Exec</span><span style="color: #24292E"> </span><span style="color: #6F42C1">WorkingDirectory</span><span style="color: #24292E">=</span><span style="color: #032F62">"client"</span><span style="color: #24292E"> </span><span style="color: #6F42C1">Command</span><span style="color: #24292E">=</span><span style="color: #032F62">"npm run build"</span><span style="color: #24292E"> /></span></span>
<span class="line"><span style="color: #24292E"></</span><span style="color: #22863A">Target</span><span style="color: #24292E">></span></span>

Hopefully this helps you in coupling ASP.NET and Vue. For a working solution, check out the GitHub repo.