When developing a new project using React, the user encountered a page refresh and directly accessed the secondary or tertiary route. The user failed to access the page, resulting in 404 or resource loading exceptions. This article analyzed the problem and summarized the solution.

Check your personal blog

background

When using webpack-dev-server as a local development server, the normal situation is to simply use the webpack-dev-server command to start, but when the project is in the following two situations, there is often a need for nested routes and asynchronous load routes:

  1. We use the React-Router routing library to build single-page application routes.
  2. usehtml-webpack-pluginPlugins will load JS dynamically<script>Tags are injected into HTML documents;

Localhost :9090 can load pages and js files, but when we need to access secondary or tertiary routes or refresh the page, such as localhost:9090/posts/92, two things can happen:

  1. Failed to load the pageCannot Get (404);
  2. Service response, but did not return the HTML file output by Webpack processing, resulting in the failure to load JS resources, the second case is shown in the figure:

So how do we deal with normal access to the page routing? This article is a summary of the process of solving the problem after the blogger traced the source and found the document configuration.

To analyze problems

After finding the problem, we will start to analyze and solve the problem. We judge that this problem is generally caused by two aspects:

  1. React-router Front-end route configuration;
  2. Webpack-dev-server service configuration;

react-router

When browserHistory is used, a real URL will be created, and initial/request processing will not be a problem. However, when you refresh the page or directly access the URL after a redirect route, you may find that the URL cannot be correctly mapped. For more information, refer to the reference document, which also provides several solutions to server configuration:

Node

const express = require('express')
const path = require('path')
const port = process.env.PORT || 8080
const app = express()

// Usually used to load static resources
app.use(express.static(__dirname + '/public'))

// Include a script tag in your application JavaScript file
// process any route in index.html
app.get(The '*'.function (request, response){
  response.sendFile(path.resolve(__dirname, 'public'.'index.html'))
})

app.listen(port)
console.log("server started on port " + port)
Copy the code

When using Node as a service, you need to use the wildcard * to listen for all requests and return the target HTML document (the HTML referencing the JS resource).

Nginx

If you are using an Nginx server, you only need to use the try_files directive:

server {
  ...
  location / {
    try_files $uri /index.html
  }
}
Copy the code

Apache

If the Apache server is used, create an. Htaccess file in the root directory of the project. The file contains the following content:

RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} ! -f RewriteCond %{REQUEST_FILENAME} ! -d RewriteRule . /index.html [L]Copy the code

The following is the configuration for the server. Unfortunately, we have not introduced the relevant server yet. We just use the built-in service of Webpack-dev-server, but we have found the problem. So the next step is to find a solution in the Webpack-dev-server documentation.

webpack-dev-server

Here we have to make fun of the official webpack-dev-server documentation. It took the blogger several times to see the problem clearly. There are also two cases:

  1. There is no changeoutput.publicPath, that is, there is no declared value in the WebPack configuration file, which is the default;
  2. Set upoutput.publicPathIs a custom value;

Click here to view the documentation

The default

By default, the output.publicPath value is not changed, just set the webpack-dev-server historyApiFallback configuration:

devServer: {
  historyApiFallback: true
}
Copy the code

If you are using the HTML5 history API you probably need to serve your index.html in place of 404 responses, which can be done by setting historyApiFallback: true

If your app uses the HTML5 History API, you may need to use index.html to respond to 404 or question requests. Just set g historyApiFallback: True

The custom value

However, if you have modified output.publicPath in your Webpack configuration, you need to specify the URL to redirect to. This is done using the historyApiFallback.index option

If you modify the output.publicPath value in the WebPack configuration file, then you need to declare the request redirection and configure the historyapIfallback.index value.

// output.publicPath: '/assets/'
historyApiFallback: {
  index: '/assets/'
}
Copy the code

Proxy

I found that using the above method could not completely solve my problem. There would always be abnormal routing request response, so the blogger continued to search for a better solution:

Click here to view the documentation

The proxy can be optionally bypassed based on the return from a function. The function can inspect the HTTP request, response, and any given proxy options. It must return either false or a URL path that will be served instead of continuing to proxy the request.

The proxy provides a way to respond to a request by returning a value from a function, with different processing for different requests, function parameters that receive HTTP requests and response bodies, and proxy configuration objects. This function must return false or the URL path to indicate how to proceed with the request. When returning the URL, the source request will be propped to the URL path request.

proxy: { '/': { target: 'https://api.example.com', secure: false, bypass: function(req, res, proxyOptions) { if (req.headers.accept.indexOf('html') ! == -1) { console.log('Skipping proxy for browser request.'); return '/index.html'; }}}}Copy the code

As configured above, you can listen for requests starting with /in the https://api.example.com field (equivalent to all requests) and determine if the Accept field in the request header contains HTML. If it does, the proxy requests /index.html, which will then return the index.html document to the browser.

To solve the problem

In summary, because output.publicPath was changed to /assets/ in webpack configuration, the blogger used webpack-dev-server Proxy to solve the problem:

const PUBLICPATH = '/assets/'
...
proxy: {
  '/': {
    bypass: function (req, res, proxyOptions) {
      console.log('Skipping proxy for browser request.')
      return `${PUBLICPATH}/index.html`
    }
  }
}
Copy the code

Listen for all front-end routes and return ${PUBLICPATH}/index.html. PUBLICPATH is output. PUBLICPATH.

In addition, bloggers are always used to declaring, although it is possible to achieve the desired effect without setting this property:

historyApiFallback: true
Copy the code