From 404s to Flawless: A Guide to Hosting React Apps with ASP.NET Core
One common pain about using Single Page Applications (SPAs) when hosted on webservers is the routing. Everything looks and functions good until you have a reason to refresh the browser page or navigate directly to a particular page without first starting from the homepage. Then you start to see errors. This does not mean your application, or the server has any problem. It's an issue with how client-side SPA routing works. SPA routing is "wired" and determined after the static files have been loaded in the browser. At this point, the application hosting server is not aware or responsible for the application routing between pages. Everything is handled by the routing setup defined in the already downloaded application static files.
There are quite a couple of recommendations on how to handle this. Some server administrators would suggest adding rewrite rules to your application host server. For Apache, your `.htaccess`? would have a rewrite rule to always serve the index.html file for all direct access to non-homepage routes managed by the client-side-router.
The `.htaccess` modification would look something like below:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
This approach may not appeal to developers, especially if they are not interested in tinkering with anything on the host server settings, but only want to work on, and deploy their code. Aside from avoiding having to make modifications to the behavior of the host server, I find the major drawback to be that the user would always be taken to the default home page when a refresh is made on the browser. This is certainly not a pleasant experience for your users.
Another recommendation is to use hash-based routing, instead of a regular history-based routing. I find that the URLs produced by this are quite 'ugly' and, it's still not always guaranteed that this approach solves the refresh error problem.
Yet, another approach is to use some form of server-side based SPA, like NextJS or Blazor. However, if I'm on some kind of time-sensitive project, it may not be the appropriate time to be making trial-and-error implementations to get NextJS to work the way you already expect your React application to work.
One of the ways I find to quickly resolve this issue is to have a server-based application host my React application. This can be done in many modern web frameworks like ASP.NET, Django and Node Express. In this post, I'll be demonstrating how you can host your React application inside an ASP.NET Core WEB API and have your application have full functionality without any errors on deployment to a host environment or when running in production.
First step, we'll create the ASP.NET Web API application using C# with .NET Core. Any modern version of .NET core should work just fine. To make this simple and easy for anyone to follow along, regardless of the operating system used, I'll be implementing this using Visual studio code (which is available on Windows, Mac and Linux). To create the application, I'll launch VS code in my chosen directory and run in my terminal of the same directory, the command:
dotnet new sln --name dotreactappsln
This would create a solution file, into which I would add the Asp.NET web Api I would be creating. Next create the web Api and add it to the created solution file. To do this, run the command:
dotnet new webapi --use-controllers --name dotnetreactapi
dotnet sln dotreactappsln.sln add dotnetreactapi
Let's confirm that the application runs fine so far, by navigating into the web API directory and running the application.
cd dotnetreactapi
dotnet watch run
Hopefully that went well, and we can see the application launched and running in the terminal and navigate to it in the browser.
Next, we'll stop the running application and create a directory inside the webapi application, where the React client app would be installed. For this example, I would be creating the React application using Vite and yarn.
mkdir clientapp
yarn create vite clientappui --template react-ts
Let's confirm that the clientapp runs as expected by first installing the required node packages `yarn install` and then `yarn run dev`
Also, before we move on, we want to make sure that in the case of pushing the entire code to a repository, e.g Github, we don't want the node_modules folder to be included. So, let's add a `.gitignore` file at the root of the application. This would be the same as the location of the `solution` file. We can generate a prepopulated `.gitignore` file using the dotnet CLI command `dotnet new gitignore`. Now we'll initialize a git repository at the root of the application before we move on to make any significant changes. Run the git command:
`git init`
Next create a `wwwroot` directory inside the wep API application and include it in the application build.
领英推荐
This is a special ASP.NET Web folder, from which our static files would be served. It would also be the build path of our React application.
Let's go ahead and configure the React application to output its build files to the `wwwroot` directory. While doing that, we can also set the port to run as we would expect on a React app created with CRA (Create-React-App), which is port 3000.
Make the adjustments on your defineConfig function in `vite.config.ts` file as below:
export default defineConfig({
server: {
port: 3000,
},
build:{
outDir: '../wwwroot',
//#emptyOutDir: true, // This will delete all files in the output directory before building
emptyOutDir: true,
},
plugins: [react()],
})
After this step, we'll make some additional changes to the `Program.cs` file of the Web API. These changes are meant to inform the .NET web API to specifically look in the `wwwroot` folder for static files to be served. Just before the `UseAuthorization` middleware, include the code snippet:
#region Static frontend serving
app.UseDefaultFiles(); // Serve the index.html file by default
string currentDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(currentDir),
RequestPath = ""
});
#endregion
You would also need to add the using statement `using Microsoft.Extensions.FileProviders;` to be able to instantiate PhysicalFileProvider class used above.
Now, go ahead and generate your static files by building your React application. In my case, running the command:
yarn build
The generated static files would be generated to the `wwwroot` folder:
At this point, if you run the web API using command `dotnet run`, you would be able to browse through the React application on the root URL. However, if you try refreshing any page that is not the root URL (i.e. index.html) you would get a `HTTP ERROR 404`
To resolve this, we would create a fallback Controller Route. It's kind of like the React catch-all wild card, only more intelligent ??. We would then map to this fallback controller route in the `Program.cs` middleware.
Fallback Controller:
In your Controllers folder, create a FallBackController, with an Index method. Instead of a View, this method would return the physical path to the index page located in your static folder. The content of this controller file would be as below:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace dotnetreactapi.Controllers
{
[AllowAnonymous]
public class FallbackController : Controller
{
public IActionResult Index()
{
return PhysicalFile(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "index.html"), "text/html");
}
}
}
With this file added, the final update is a small change to the `Program.cs` file. Just before calling the app.Run() middleware, add a fallback mapping to the just created controller:
.
.
.
app.MapFallbackToController("Index", "Fallback"); // Fallback controller mapping
app.Run();
And that's it. Rebuild your application to ensure the new code changes are compiled.
With your published React files in the wwwroot folder, you can now serve your react application as part of your ASP.NET code, having full refresh functionality and direct non-homepage route access, without any 404-Not Found error.