Adding a React App to a .NET Core MVC App

I am one to think that not everything I build has to be a Single Page Application (SPA). Recently I was building a project for a client, and I decided that only one section of the app needed to be a SPA and the rest of the app a "traditional" MVC app, so I started the app with the "MVC" part and later added the "SPA". This is how I did it.

Adding a React app to your existing .NET Core MVC app is easier than you might think

One thing to note is that I am using VS Code and the .NET Core CLI on macOS.

The Existing .NET Core MVC App#

If you wish to follow along, clone the following repo dotnet-core-mvc-react (intial-app branch. Yes, branch name is misspelled >_<) and open the project.

To run the project in VS Code, open the integrated terminal and type the following

Our awesome app has three views under Home controller and configured with a custom 404 page.

You get a new feature request and it is a perfect candidate for a React app. Fortunately, adding a React app is easier than you might think.

Go ahead and stop the project (if you have it running) by pressing Ctrl + C in your terminal.

Creating the React app#

Rather than creating the app from scratch or using Create React App (CRA), we will create a new .NET Core project with React template. This default template will have React Router installed making it easier for us.

Open your terminal, navigate to a different directory and type the following

Now in Finder navigate to this newly created project directory (temp-react-core) and copy ClientApp directory to the root of our actual project. Also copy WeatherForecastController.cs to the Controllers directory and WeatherForecast.cs to the Models directory of our project.

You will end up with the following project structure.

Now back to VS Code, open WeatherForecastController.cs and WeatherForecast.cs and change the namespace to match our project's namespace. You will end up with the following

Still in VS Code, open dotnet-core-mvc-react.csproj and replace it with the following contents

All of that is just configuration telling Webpack to build the React app. The line <SpaRoot>ClientApp\</SpaRoot> tells .NET Core where the React app lives.

Another thing to note is that we are adding a new package Microsoft.AspNetCore.SpaServices.Extensions.

Open Terminal again and type the following to restore this new package

Now open Startup.cs and add the following under the ConfigureServices and Configure methods

The order of configurations under the Configure method matters, if you add app.UseSpa(...) before app.UseEndpoints(...) then React will take over routing and none of your MVC routes will work.

At this point we have configured the React app to work nicely with .NET Core. In the background the framework will run react-scripts which in turn run CRA to build and serve React in the development environment.

Running the app for the first time#

Now that everything is set up we can safely install and update the JavaScript packages. Open Terminal, navigate to ClientApp directory within our project, and type the following to install

Optional: To update the packages I use npm-check. Go ahead and use that tool or the one of your preference to update the JavaScript packages.

Still in Terminal, go back to the root of the project and run the app

Now try to access the React app by typing the following: https://localhost:5001/ClientApp. You will notice you hit a blank page, but if you click on the menu items they actually take you to their respective pages.

When you click Home, notice the URL changes to https://localhost:5001. If you were to hit refresh then the React home will go away and load the Home View in HomeController. This is not good.

Now, if you were to type a route that does not exist (say https://localhost:5001/dfdf) you will get a blank page. React took over our custom 404 page and just displays a blank page. In fact when we accessed https://localhost:5001/ClientApp we got React Router's "404 page".

Fixing the Home issue#

Open index.js under ClientApp -> src directory and assign baseURL to "ClientApp".

Back to your browser, access https://localhost:5001/. You will get the correct non-React home. Now access https://localhost:5001/ClientApp, and you will get the React home.

Fixing our custom 404 page#

Since React is taking over the custom 404 page, we will have to handle 404s within React. There are two options for taking care of this issue.

Option 1#

Let's create a Not Found component and hook it up to React Router.

Create a new file under ClientApp -> src -> Components directory and call it NotFound.js. Copy the following:

Now open App.js under ClientApp -> src directory and replace it with the following:

In order to display the custom 404 page correctly, we would need to import Switch from react-router and enclose our routes with it.

Then we import the NotFound component and add it after the last route. If we add it before the first route or in between, and you try accessing any other route after it, then the NotFound component will display instead of the requested component. This is because the NotFound Route does not have a path and is basically a "catch-all" route.

Now, if we were to not include the Switch, then the NotFound component would display alongside every other route.

Try accessing https://localhost:5001/dfdf, and you will get the 404 page from React. Nice!!

Option 2#

Open NotFound.js and replace the contents with the following:

Now open HomeController.cs and comment out the annotation above NotFoundPage action result

Finally open Startup.cs and comment out the following line under the Configure method.

Try accessing https://localhost:5001/dfdf and you will get the 404 page served by .NET.

Final Repo#


If you already have authentication set up, any calls from within React to your Web API will already be authenticated since cookies attach automatically to ajax calls, including the auth cookies.

More than likely you will not want to access your awesome new feature with ClientApp as the route. Just change the baseURL under index.js to whatever you want want it to be, say AwesomeNewFeature

With this change we can access the app like so https://localhost:5001/AwesomeNewFeature


That is it. You made it through the end. Thanks for reading.

Let me know in the comments any questions, corrections or even praise. I look forward to reading them.

You can access my website to learn more about me at or follow me on Twitter or GitHub. To read more of my blog posts follow