I want to configure .Net Core application with react (SPA). Everything is working fine. When I use react rout to load component/page it works fine UNTIL I refresh browser. If I refresh browse I get "Cannot GET /Path" (404) error. I know this page is not there physically it will be served from JS (react), what configuration I need to do to avoid this error? Also react hot-reload is no longer working.
I have few pages in .Net Core application and I am loading react bundles on some of the pages. Please find the code for both .Net and React application below.
How do I retain page and it's state on hard-refresh and on react hot-reload?
.Net Application
Startup.cs
namespace AspnetCoreReact.Web
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication();
services.AddInfrastructure(Configuration, Environment);
services.AddScoped<ICurrentUserService, CurrentUserService>();
services.AddHttpContextAccessor();
services.AddHealthChecks()
.AddDbContextCheck<AspNetReactDbContext>();
ConfigureCookieSettings(services);
// Add memory cache services
services.AddMemoryCache(); // To do: Change to distributed cache later
services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
services.AddMvc();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddHttpContextAccessor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHealthChecks("/health");
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "Scripts";
if (env.IsDevelopment())
{
// Ensure that you start webpack server - npm run build
string webpackDevServer = "https://localhost:5010/";
spa.UseProxyToSpaDevelopmentServer(webpackDevServer);
}
});
}
}
}
Views/Home/Index.cshtml (MVC view, there are other view where I am loading different react bundle)
@{
ViewData["Title"] = "Home Page";
ViewBag.NoContainer = true;
}
<div class="home-header">
<h2 class="home-header-subtitle">Welcome to the</h2>
<h1 class="home-header-title">AspnetCoreReact</h1>
<div class="home-header-actions">
<a class="btn btn-primary home-header-button" asp-area="" asp-controller="Editor" asp-action="Index">Try the demo</a>
</div>
<div id="home">
</div>
</div>
<script src="~/spa/home.bundle.js" type="module"></script>
React App
webpack.dev.config.ts
import path from "path";
import webpack from "webpack";
import * as fs from 'fs';
const modulePath = './Scripts/modules/'
const entries = {
home: modulePath + "HomeModule.tsx",
editor: modulePath + "EditorModule.tsx"
};
const config: webpack.Configuration = {
mode: "development",
output: {
filename: "[name].bundle.js",
publicPath: "/spa",
},
entry: entries,
module: {
rules: [
{
test: /\.(ts|js)x?$/i,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
},
},
},
...
{
test: /\.html$/,
use: [{
loader: "html-loader",
options: {
minimize: true
}
}]
}
],
},
resolve: {
extensions: [".tsx", ".jsx", ".ts", ".js"],
},
plugins: [
],
devtool: "inline-source-map",
devServer: {
contentBase: path.join(__dirname, "build"),
historyApiFallback: true,
port: 5010,
open: false,
hot: true,
https: {
key: fs.readFileSync('./Scripts/generated/webpack_cert.key'),
cert: fs.readFileSync('./Scripts/generated/webpack_cert.crt'),
}
}
};
export default config;
HomeModule.tsx (There multiple such modules which are loaded on different pages of .Net app)
import * as React from 'react';
import * as ReactDom from 'react-dom';
import HomeComponent from '../components/HomeComponent';
const homeRoot = document.getElementById('home');
ReactDom.render(<HomeComponent msg={'Hello World!!!'} />, homeRoot);
HomeComponent.tsx
const HomeComponent = () => {
return (
<Router>
<NavMenu />
<Switch>
<Route exact path="/" component={HomeComponent} />
<Route exact path="/AnotherPage" component={AnotherPageComponent} />
</Switch>
</Router>
)
}
export default HomeComponent
Added fall-back endpoint in Startup.cs -> Configure, and now it is working as expected.
endpoints.MapFallbackToController("Index", "Home");