8

I have a stock aspnetcore and reactjs app, generated from the starter template (dotnet new react). I would like the SPA app to be served from a subpath off the root url; e.g. instead of the sample app being https://localhost:5001/counter I'm looking for it to instead be served from https://localhost:5001/myapp/counter.

I changed the Startup.cs from:

app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });

to this:

app.Map(new Microsoft.AspNetCore.Http.PathString("/myapp"), appMember =>
            {
                appMember.UseSpa(spa =>
                {
                    spa.Options.SourcePath = "ClientApp";

                    if (env.IsDevelopment())
                    {
                        spa.UseReactDevelopmentServer(npmScript: "start");
                    }
                });
            });

This sort of works. If I browse to https://localhost:5001/myapp/ it appears to load the index.html, but the static files are attempting to load from the root path and not the subpath.

What needs to be changed so that the react app uses the subpath as the root? I'd like this to work both in the interactive VS dev environment and when deployed, likely on IIS. It seems like it's close but I'm missing something.

Sample demo of the solution is available here: https://github.com/petertirrell/mvc-spa-demo/tree/master/mvc-spa-demo

Thanks!

2
  • Did you solve this by any chance? Commented Aug 30, 2020 at 0:13
  • I just finished testing this on Azure now. Please see my answer below, BR Commented Sep 26, 2020 at 16:39

3 Answers 3

4

Start with moving app to sub-path by adding this to top of package.json:

"homepage": "/myapp/", 

When running npm start inside ClientApp folder, app is now serving http://localhost:3000/myapp

Then change Startup.cs like this:

First remove

app.UseSpaStaticFiles()

then add

        const string spaPath = "/myapp";
        if (env.IsDevelopment())
        {
            app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(spaPath)
                               || ctx.Request.Path.StartsWithSegments("/sockjs-node"),
                client =>
            {
                client.UseSpa(spa =>
                {
                    spa.Options.SourcePath = "ClientApp";
                    spa.UseReactDevelopmentServer(npmScript: "start");
                });
            });
        }
        else
        {
            app.Map(new PathString(spaPath), client =>
            {
                // `https://github.com/dotnet/aspnetcore/issues/3147`
                client.UseSpaStaticFiles(new StaticFileOptions()
                {
                    OnPrepareResponse = ctx =>
                    {
                        if (ctx.Context.Request.Path.StartsWithSegments($"{spaPath}/static"))
                        {
                            // Cache all static resources for 1 year (versioned file names)
                            var headers = ctx.Context.Response.GetTypedHeaders();
                            headers.CacheControl = new CacheControlHeaderValue
                            {
                                Public = true,
                                MaxAge = TimeSpan.FromDays(365)
                            };
                        }
                        else
                        {
                            // Do not cache explicit `/index.html` or any other files.  See also: `DefaultPageStaticFileOptions` below for implicit "/index.html"
                            var headers = ctx.Context.Response.GetTypedHeaders();
                            headers.CacheControl = new CacheControlHeaderValue
                            {
                                Public = true,
                                MaxAge = TimeSpan.FromDays(0)
                            };
                        }
                    }
                });

                client.UseSpa(spa =>
                {
                    spa.Options.SourcePath = "ClientApp";
                    spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions()
                    {
                        OnPrepareResponse = ctx => {
                            // Do not cache implicit `/index.html`.  See also: `UseSpaStaticFiles` above
                            var headers = ctx.Context.Response.GetTypedHeaders();
                            headers.CacheControl = new CacheControlHeaderValue
                            {
                                Public = true,
                                MaxAge = TimeSpan.FromDays(0)
                            };
                        }
                    };
                });
            });
        }

Don't forget to clear browser history before testing changes for the first time on e.g. Azure.

Sign up to request clarification or add additional context in comments.

Comments

4

You can do so by having:

services.AddSpaStaticFiles(configuration =>
{
    configuration.RootPath = "ClientApp/build";
});

in your ConfigureServices and:

string spaPath = "/myapp";
if (env.IsDevelopment())
{
    app.MapWhen(y => y.Request.Path.StartsWithSegments(spaPath), client =>
    {
        client.UseSpa(spa => 
        {
            spa.UseReactDevelopmentServer(npmScript: "start");
        });
    });
}
else
{
    app.Map(new PathString(spaPath), client =>
    {
        client.UseSpaStaticFiles();
        client.UseSpa(spa => {});
    });
}

It should be noted that in development we use .MapWhen because .Map would cause your static files to be available at /myapp/myapp/[file] as opposed to /myapp/[file].

1 Comment

How can the React dev server proxy be setup with .Net Framework 4.6? With .Net Core it's simple to use UseReactDevelopmentServer, but this isn't available in .Net Framework.
0

I combined some of each answer to get it working. You need to add the "homepage": "/myapp" to the package.json as well as the config changes to Startup.cs. I used the simpler config provided in Shoe's answer without all the extra caching and sockets directives, as I don't need those.

Because my application also used React Router for SPA routing under /myapp I also needed to add basename to the root BrowserRouter:

<BrowserRouter basename="/myapp" >...</BrowserRouter>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.