Search code examples
vue.jsasp.net-coresingle-page-applicationstatic-files

Serving static index.html from the spa folder


I use .netcore server side and vuejs 2.

I have generated html files for some routes of my vuejs, that I placed directly in the dist folder:

enter image description here

I can access the html files with http://my-domain/en/home/index.html, but calling http://my-domain/en/home (without the index.html) won't serve the html file. Instead, it will return the equivalent spa page.

What can I do to fix this? I want the server to return the html file if it exists in priority, otherwise return the normal spa website.

Here is part of my startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    // ...

    // In production, the vue files will be served from this directory
    services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; });

    // ...
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...

    // WebRootPath == null workaround. - from https://github.com/aspnet/Mvc/issues/6688
    if (string.IsNullOrWhiteSpace(env.WebRootPath))
    {
        env.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "ClientApp", "dist");
    }

    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = ctx =>
        {
            const int durationInSeconds = 60 * 60 * 24;
            ctx.Context.Response.Headers[HeaderNames.CacheControl] =
                "public,max-age=" + durationInSeconds;
        }
    });
    app.UseSpaStaticFiles();

    // ...

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

        if (env.IsDevelopment())
        {
            spa.UseVueCli(npmScript: "serve", port: 8085);
        }
    });
}

EDIT: On top of @Phil 's response, I needed to provide a FileProvider because UseDefaultFiles wasn't looking in the right folder:

app.UseDefaultFiles(new DefaultFilesOptions
{
    FileProvider = new PhysicalFileProvider(env.WebRootPath) // important or it doesn't know where to look for
});

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        const int durationInSeconds = 60 * 60 * 24;
        ctx.Context.Response.Headers[HeaderNames.CacheControl] =
            "public,max-age=" + durationInSeconds;
    },
    FileProvider = new PhysicalFileProvider(env.WebRootPath) // same as UseDefaultFiles
});

Solution

  • You need to tell the server to use Default Files

    With UseDefaultFiles, requests to a folder search for:

    default.htm
    default.html
    index.htm
    index.html

    app.UseDefaultFiles(); // this must come first
    app.UseStaticFiles(...
    

    This basically sets up an interceptor for requests on a folder (like your en/home) and if it finds any of the above filenames, will rewrite the URL to folder/path/{FoundFilename}.

    If you want to avoid searches for anything other than index.html, you can customise it

    DefaultFilesOptions options = new DefaultFilesOptions();
    options.DefaultFileNames.Clear();
    options.DefaultFileNames.Add("index.html");
    app.UseDefaultFiles(options);
    

    Note the important information about ordering

    Important

    UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL rewriter that doesn't actually serve the file. Enable Static File Middleware via UseStaticFiles to serve the file.