Am trying to create a VueJS SPA with ASP.NET core in the backend
Since dotnet SDK
version 3.0.100
no longer supports Vue, I created a React app
dotnet new react
and swapped the ClientApp
with a Vue app
rm -rf ClientApp
vue create frontend
Everything works perfectly except for 1 thing
Whenever I launch the app http://localhost:5000 and click on any Vue Router link, say http://localhost:5000/about (it works) but if I refresh I get an expected 404
Cannot GET /about
Same goes if I typed the URL into the browser and access directly
It's worth noting that the default React app doesn't face this problem, I compared everything between the files of the two ASP.NET apps using this tool and found no differences
Now Vue Router handles routes by Javascript because it's using the #!hash behind the scenes, so ASP.NET router tries to match the URL to an AboutController@index which doesn't exist hence the 404
Researching revealed that I should set a wildcard fallback to the SPA router in Startup.cs
, but all the answers I found here, here, here, this question, answer here, this question and this one are using app.UseMvc()
and I don't have that enabled, I am using app.UseEndpoints
instead
I also tried searching GitHub but same results
This Answer recommends I stick to app.UseEndPoints
and Microsoft migration guide too
The Vue Router Documentation talks about this very issue and proposes a solution for .NET apps using web.config
for IIS like the answer here but am running Kestrel server on a Linux machine so this takes no effect
Here's my Startup.cs
file
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
namespace spa
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// In production, the Vue files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "frontend/dist";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "frontend";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "serve:bs");
}
});
}
}
}
I pushed this app to GitHub here to make it easy to reproduce just in case I forgot something
How can I achieve the same effect of this
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" });
});
using app.UseEndpoints
?
Just in case this is relevant, here's my Vue router
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import NotFound from './views/NotFound.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import( /* webpackChunkName: "about" */ './views/About.vue')
},
{
path: '/forecast',
name: 'forecast',
component: () => import( /* webpackChunkName: "forecast" */ './views/Forecast.vue')
},
{
path: '*',
component: NotFound
}
]
})