15

I am learning full-stack web development with .NET Core and React, so I created an ASP.NET Core Web Application project with React template in Visual Studio 2019.

At some point I noticed that if I request a non-existing URL, I don’t get an error or 404 page as I would expect but rather the index.html as the response.

I want that my backend returns a 404 status code whenever a non-existing URL is called.

I tried to fix this by adding a React Switch tag around Route tags in App.js and added a component that is shown when the requested URL doesn’t match the defined routes:

import React, { Component } from 'react';
import { Route, Switch } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import NoMatch from './components/NoMatch'; // <-- Added this

export default class App extends Component {
  static displayName = App.name;

  render () {
    return (
      <Layout>
        <Switch> // <-- Added this
          <Route exact path='/' component={Home} />
          <Route path='/counter' component={Counter} />
          <Route path='/fetch-data' component={FetchData} />
          <Route component={NoMatch} /> // <-- Added this
        </Switch> // <-- Added this
      </Layout>
    );
  }
}
import React from 'react';

export default function NoMatch() {
  return (
    <div>
      <h1>Error</h1>
      <h2>404</h2>
      <p>Page was not found</p>
    </div>
  );
}

But I think it is not a real solution to the problem since I later discovered that sending a request to a non-existing API via fetch function also returns index.html as a response. The project has an example component FetchData that has a constructor with fetch function. Replacing the example URL with a non-existing path reproduces the behavior:

constructor (props) {
  super(props);
  this.state = { forecasts: [], loading: true };

  fetch('api/nonexistingpath') // <-- Changed this URL
    .then(response => response.json())
    .then(data => {
      this.setState({ forecasts: data, loading: false });
    });
}

So, I thought that the problem is in the .NET Core backend. I went to the Startup file and tried to fix this behavior there, I noticed that when removing everything from the parentheses of this piece of code:

app.UseMvc(routes =>
{
  routes.MapRoute(
    name: "default",
    template: "{controller}/{action=Index}/{id?}");
});

doesn’t change the behavior of the program. However, if I remove this code entirely, the frontend will load, but the fetch function doesn’t return the data, it again returns the index.html. I tried to change the template, but it seems to me, that it has no effect on programs behavior.

I am really confused, am I getting something wrong? Wouldn’t it be the expected default behavior to return an error or 404 page when requesting a non-existing URL? I couldn’t find much on the internet either.

https://stackoverflow.com/a/53687522/10951989

I found this answer, but it doesn’t give any references or explanation on why it is the default behavior.

https://stackoverflow.com/a/44164728/10951989

I tried to use code from this answer, but it blocks everything that is not an API call. Can somebody help me?

Thank you in advance!

Update #1

Ok, after long attempts, I seem to have found the solution that works for me:

app.MapWhen(x => x.Request.Path.Value.StartsWith("/api"), builder =>
{
  app.UseMvc();
});

app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
{
  app.UseSpa(spa =>
  {
    spa.Options.SourcePath = "ClientApp";

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

1 Answer 1

14

The ASP.NET Core + React template creates a project that does two things at once:

  1. Acts as a web server to host the static files (your React app)
  2. Serves API responses (your C# controllers)

The behavior you're noticing (serving index.html instead of returning 404 for missing pages) is part of #1. Changing your React routing code didn't make a difference because it's server behavior. ASP.NET Core calls this a "SPA fallback route". This excellent blog post calls it "configurable static" behavior:

A web server can be configured to respond with an alternative file if the requested resource doesn’t actually exist on the disk. If a request for /bears comes in and the server has no bears file, the configuration can tell it to respond with an index.html file instead.

The goal is to make it easier to have "pretty" URLs. If your React single-page app describes a route like /counter, but there is no counter.html on the server, then someone navigating directly to /counter from a bookmark or refreshing their browser will see a 404 error message. By configuring the server (ASP.NET Core in this case) to serve index.html instead of 404, the React app will be loaded and can respond correctly to the path in the URL.

If you comment out the app.UseSpaStaticFiles() line in your Startup class, your server should start returning real 404s. But that leaves the above problem with frontend routing.

Here's a snippet I use in my projects to serve index.html except when the request path starts with /api:

    app.UseMvc();

    app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
    {
        app.Run(async (context) =>
        {
            context.Response.ContentType = "text/html";
            context.Response.Headers[HeaderNames.CacheControl] = "no-store, no-cache, must-revalidate";
            await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "index.html"));
        });
    });
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the explanation, after reading the linked blog post and thinking about your answer I think I finally understand why the server always returns the index.html. I also tried to use your code snippet, but it didn’t do what I expected. At first it threw me an expection because env.WebRootPath equaled null, since I don’t have wwwroot folder in my project, so I replaced env.WebRootpath with Directory.GetCurrentDirectory and then combined it with "ClientApp/public/index.html". But now all I see in the browser is a blank index.html without even the navigation bar.
@AlexRekowski Hmm, I'd check the browser's network panel and JavaScript console to see if there were any errors.
I added code that works for me to the question as an update. Not sure if that’s the best solution though.
@AlexRekowski Yep, that makes sense.
You get blank page because requests to your .js/.css files is being handled by that snippet and always returning index.html
|

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.