How to Build a True Multi-Page Website Using ASP.NET, VUE.js, Vite.js: Getting ASP.NET MVC and Vite.js to play nicely together

David W. Gray
7 min readDec 18, 2023

--

This article is part of a series where I show how to build a “true” multipage website using ASP.NET, Vue.js, and Vite.js. The first article describes the requirements for the website and why I’ve landed on this specific combination of technologies and requirements. In this article, I’ll walk through setting up the ASP.NET core MVC website, adding the Vue.js/Vite.js client, and getting them to play nicely together.

Setting up the MVC application

I’m going to start with the “ASP.NET Core Web App (Model-View-Controller)” template.

Typing dotnet new mvc -o AspNetViteMpa from a Visual Studio Command prompt is the easiest way to create a project from the template. Microsoft has an excellent tutorial here if you want more details or prefer to use the Visual Studio UI.

After running the application, you’ll see I have a cleanly styled multi-page website with a Home Page and a Privacy tab. Navigation is provided by the menu bar at the top.

ASP.NET MVC Template Website

AspNetViteMpa/vanilla-mvc is the GitHub tag for the project at this point.

Setting up the Vue.js Client

The next step is to create a simple Vue.js client. Since I use TypeScript wherever possible, I’d like to start a template that includes that. This is as easy as running the create-vite tool in my Asp.Net project directory:

yarn create vite ClientApp — template vue-ts

or

npm yarn create vite@latest ClientApp — template vue-ts

I can then run the Vue.js client stand-alone:

yarn dev

or

npm run dev
Vite.js/Vue.js Template Website

AspNetViteMpa/initial-vite-client is the GitHub tag for the project at this point.

But at this point, running the ASP.NET app will completely ignore the Vue.js client (and vice versa). They’re two totally separate applications that just happen to be running in the same directory structure. Notice, especially, that they’re running against different ports on localhost.

So, how do I get them to cooperate with each other?

Cleaning up the Asp.Net Template

First, I am going to remove the styling in the MVC app. In my real-world example of this scenario, I’m using Bootstrap.js for styling, but it’s provided by the client app build process, so even if I end up going that far in this series, I don’t want the version of Bootstrap.js that the ASP.NET MVC template hard-coded. For this simple example, I’m not using a CSS framework at all (and yes, it will look pretty ugly, but that’s not the point of this project).

It took me a couple of tries (Remove Bootstrap & Clean Up More Cruft) to get everything, but the main point is to remove all the files from wwwroot/lib — the fact that the template hard-codes third-party libraries in this day and age is a problem, so this step solves that problem as well. Then, we eliminate all the bootstrap classes in the .cshtml and generally simplify the _Layout.cshtml file.

Wiring up the Client App

The core part of this step is to replace the body of our index.cshtml file with the code to load the Vuejs application.

<environment include=”Development”>
<script type="module" src="https://localhost:7256/src/main.ts"></script>
</environment>
<environment include="Production">
@section Styles {
<link rel="stylesheet" asp-href-include="/vite-client/assets/index-*.css">
}
@section Scripts {
<script asp-src-include="/vite-client/assets/index-*.js"></script>
}
</environment>
<div id="app"></div>

Note that I’m doing something different in Development than in Production. The Vite.js interactions are pulled from Backend Integration | Vite (vitejs.dev), which I integrated with ASP.NET’s tags to specify different behaviors in Development and Production environments. I’ll dig into this more in a future post where I describe how I minimize duplicate code in my .cshtml files as I add more pages.

The other important thing about this step is that Vite.js is a dev server. I also need to ensure this will work in production. On top of the production changes in the index.cshtml file, I must also build the client when I publish the site. I do this by adding a CompileClient target to the project file that fires before the Publish step and runs yarn and yarn build to invoke Rollup.js to build the client. Rollup.js is the bundler that Vite.js uses by default to build.

<Target Name='CompileClient' BeforeTargets="Publish">
<Exec WorkingDirectory="./ClientApp" Command="yarn" />
<Exec WorkingDirectory="./ClientApp" Command="yarn build" />
</Target>

The last minor detail is that the MVC template no longer includes a @Style section for some reason, so I have to include that manually in the <head> section of the _Layout.cshtml file. I’m not sure how this was lost, but it’s been a feature of ASP.NET MVC since at least version 3 that the default layout includes a @Style section.

At this point, I have an (ugly) ASP.NET MVC/Vue.js Hybrid page limping along.

Initial stab at ASP.NET MVC + Vite.js/Vus.js page

The (now ugly) menu at the top is served up directly by ASP.NET MVC and the body of the page is a Vue.js root. Everything looks about right, except that the Vue logo isn’t rendering.

Setting up a Proxy

Why isn’t the logo rendering when everything else is, including the other images? This is because I’m still running two servers listening to two different ports on localhost. The core vue.js app is rendering (mostly) correctly because, on my Index.cshtml page, I’m specifying the port of the Vite.js server to load the client code from. Even the icons render because they’re just embedded .svgs.

// CommunityIcon.Vue
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

But in the place in the code where I’m loading the logo, the code is just asking the site that’s hosting the page (the ASP.NET site) to serve up the logo.svg asset.

<img alt=”Vue logo” class=”logo” src=”./assets/logo.svg” width=”125" height=”125" />

That results in a request to the ASP.NET MVC server, which runs on port 7025, rather than a request to the Vite.js server, which runs on port 7256. I tracked this down by seeing the error in the console, then reloading with the network tab on and clicking on the error, which gave me the detail of which port the request was made on.

Console Error
Network Tab with the logo.svg request selected

This tells me clearly that I want to set up a proxy.

There are several ways that a proxy might help solve this problem. The general idea for this kind of proxy is that one server needs to be the primary server that handles all communication with the browser, and when it gets a request that it “knows” it can’t handle, it should forward it to the secondary server to handle. Thus, the first server acts as a proxy for the second server. Since the main web page is being served up by ASP.NET, the most straightforward solution is to have that serve as a proxy for the Vite.js server. It happens that there is a Nuget package that does exactly what I want: Eptagone/Vite.AspNetCore: Small library to integrate Vite into ASP.NET projects (github.com). It also has some other features I’ll use in future steps, which is why I’m using this particular solution.

I added in the Vite.AspNetCore NuGet package and configured the proxy as per the documentation by setting the port in my appsetting.Development.json file to the port the Vite dev server uses. I did this by hardcoding the port number in the ASP.NET configuration and importing the JSON file to my vite.config.ts file as a JavaScript object. This let me look up the value in the client configuration. I went through those convolutions so that the hard-coded port is only in one place in the code. That way, if a future version of me arbitrarily decides that 7256 is no longer a good port for the Vite server to listen to, he won’t also have to remember to change it in two places.

I also added the code to invoke the proxy middleware in my program.cs file.

And now the Vue.js Logo is being served up correctly.

Home page with ASP.NET serving the images and Vite.js serving assets

Confirm that the Client can Call ASP.NET APIs

The very last thing I did in this phase was confirm that my Vue.js client code could call APIs on the ASP.NET server.

I did this in the simplest possible way. I added a line to my Program.cs to return a hardcoded string when a specific get request was called:

app.MapGet(“/api/hello”, () => “Hello from the Server”);

This is not something I’d do in a production environment, but it’s a cheap way to prove that the system is working. Then, I added a call to that endpoint in the client code and rendered the result. You can see that it works in the screenshot above.

With that, I have a website that uses ASP.NET MVC to serve up pages (the privacy page is still a pure MVC page), can add a Vue.js application to a page, uses Vite.js to serve up assets, and can call ASP.NET core API to server API requests. This application can also be deployed as a “production” website to a server.

AspNetViteMpa/initial-integration is the GitHub tag for the project at this point.

Next time, I’ll glue this post together with the previous post about Vite.js multi-page applications with the shenanigans to generalize my .cshtml code so I don’t need a different .cshtml file for every page, which will result in a generally functional multi-page website of the flavor that I, and hopefully you, are looking for.

--

--

David W. Gray

I am a software engineer, mentor, and dancer. I'm also passionate about speculative fiction, music, climate justice, and disability rights.