How to combine multiple JS apps with .NET Core (Create React APP + Next JS + .NET Core)
Hello everyone! A couple of months ago, we had a task to create a Landos for our online service. Our stack is Create React App + .Net Core. Googling a little, we decided that we wanted to make Landos on Next JS, but the question arose – how to make it all together.
We wanted the application to open from the link: yourdomain.com/app
, and all other links would lead to Landos.
First, in the folder where you have the ClientApp you need to create the LandingApp folder where you will add the second project. (If anything, the folders can be named whatever you like)
After you add the second project, you need to update a little startup.cs, to .Net Core could “switch traffic” from one project to another.
Disable endpoint routing in the method ConfigureServices
services.AddMvc(options =>
{
options.EnableEndpointRouting = false;
});
Into method Configure add static files for CRA
app.UseSpaStaticFiles(new StaticFileOptions() {
RequestPath = "/app"
});
Add mapper, which when following the link /app
will open our application
app.MapWhen(context => context.Request.Path.Value.StartsWith("/app"), builder =>
{
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
"app",
new { controller = "", action = "app" }
);
});
builder.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
});
Then, by default, we open our Next JS application
app.UseSpa(spa =>
{
spa.Options.SourcePath = "LandingApp";
// Для простоты билда, мы забилдили лендос отдельно
// И там скрывается ссылка по типу
// https://yourapp.azurewebsites.net
var url = Configuration.GetSection("urls")["landingUrl"];
if (env.IsDevelopment())
{
url = "http://localhost:3005";
}
spa.UseProxyToSpaDevelopmentServer(url);
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "dev");
}
});
The whole Configure method
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
_env = 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.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
await next();
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles(new StaticFileOptions() {
RequestPath = "/app"
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.MapWhen(context => context.Request.Path.Value.StartsWith("/app"), builder =>
{
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
"app",
new { controller = "", action = "app" }
);
});
builder.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "LandingApp";
var url = Configuration.GetSection("urls")["landingUrl"];
if (env.IsDevelopment())
{
url = "http://localhost:3005";
}
spa.UseProxyToSpaDevelopmentServer(url);
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "dev");
}
});
}
Next, we need to tell CRA where to get the bundle from. now it is not at the root, but in /app
… For this, we are in package.json specify the property homepage: "/app/"
If you are using the library history then you need to specify more basename: '/app'
import { connectRouter } from 'connected-react-router';
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory({
basename: '/app',
});
export const routerReducer = connectRouter(history);
And that’s about it 🙂