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
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
Open Terminal again and type the following to restore this new package
Startup.cs and add the following under the
The order of configurations under the
Configure method matters, if you add
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#
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#
index.js under ClientApp -> src directory and assign
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.
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:
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
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
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!!
NotFound.js and replace the contents with the following:
HomeController.cs and comment out the annotation above
NotFoundPage action result
Startup.cs and comment out the following line under the
Try accessing https://localhost:5001/dfdf and you will get the 404 page served by .NET.
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
index.js to whatever you want want it to be, say
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.