| 1 | <h1 align="center">connect-history-api-fallback</h1>
|
|---|
| 2 | <p align="center">Middleware to proxy requests through a specified index page, useful for Single Page Applications that utilise the HTML5 History API.</p>
|
|---|
| 3 |
|
|---|
| 4 | <h2>Table of Contents</h2>
|
|---|
| 5 |
|
|---|
| 6 | <!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
|
|---|
| 7 |
|
|---|
| 8 | - [Introduction](#introduction)
|
|---|
| 9 | - [Usage](#usage)
|
|---|
| 10 | - [Options](#options)
|
|---|
| 11 | - [index](#index)
|
|---|
| 12 | - [rewrites](#rewrites)
|
|---|
| 13 | - [verbose](#verbose)
|
|---|
| 14 | - [htmlAcceptHeaders](#htmlacceptheaders)
|
|---|
| 15 | - [disableDotRule](#disabledotrule)
|
|---|
| 16 |
|
|---|
| 17 | <!-- /TOC -->
|
|---|
| 18 |
|
|---|
| 19 | ## Introduction
|
|---|
| 20 |
|
|---|
| 21 | Single Page Applications (SPA) typically only utilise one index file that is
|
|---|
| 22 | accessible by web browsers: usually `index.html`. Navigation in the application
|
|---|
| 23 | is then commonly handled using JavaScript with the help of the
|
|---|
| 24 | [HTML5 History API](http://www.w3.org/html/wg/drafts/html/master/single-page.html#the-history-interface).
|
|---|
| 25 | This results in issues when the user hits the refresh button or is directly
|
|---|
| 26 | accessing a page other than the landing page, e.g. `/help` or `/help/online`
|
|---|
| 27 | as the web server bypasses the index file to locate the file at this location.
|
|---|
| 28 | As your application is a SPA, the web server will fail trying to retrieve the file and return a *404 - Not Found*
|
|---|
| 29 | message to the user.
|
|---|
| 30 |
|
|---|
| 31 | This tiny middleware addresses some of the issues. Specifically, it will change
|
|---|
| 32 | the requested location to the index you specify (default being `/index.html`)
|
|---|
| 33 | whenever there is a request which fulfills the following criteria:
|
|---|
| 34 |
|
|---|
| 35 | 1. The request is a `GET` or `HEAD` request
|
|---|
| 36 | 2. which accepts `text/html`,
|
|---|
| 37 | 3. is not a direct file request, i.e. the requested path does not contain a
|
|---|
| 38 | `.` (DOT) character and
|
|---|
| 39 | 4. does not match a pattern provided in options.rewrites (see options below)
|
|---|
| 40 |
|
|---|
| 41 | ## Usage
|
|---|
| 42 |
|
|---|
| 43 | The middleware is available through NPM and can easily be added.
|
|---|
| 44 |
|
|---|
| 45 | ```
|
|---|
| 46 | npm install --save connect-history-api-fallback
|
|---|
| 47 | ```
|
|---|
| 48 |
|
|---|
| 49 | Import the library
|
|---|
| 50 |
|
|---|
| 51 | ```javascript
|
|---|
| 52 | var history = require('connect-history-api-fallback');
|
|---|
| 53 | ```
|
|---|
| 54 |
|
|---|
| 55 | Now you only need to add the middleware to your application like so
|
|---|
| 56 |
|
|---|
| 57 | ```javascript
|
|---|
| 58 | var connect = require('connect');
|
|---|
| 59 |
|
|---|
| 60 | var app = connect()
|
|---|
| 61 | .use(history())
|
|---|
| 62 | .listen(3000);
|
|---|
| 63 | ```
|
|---|
| 64 |
|
|---|
| 65 | Of course you can also use this piece of middleware with express:
|
|---|
| 66 |
|
|---|
| 67 | ```javascript
|
|---|
| 68 | var express = require('express');
|
|---|
| 69 |
|
|---|
| 70 | var app = express();
|
|---|
| 71 | app.use(history());
|
|---|
| 72 | ```
|
|---|
| 73 |
|
|---|
| 74 | ## Options
|
|---|
| 75 | You can optionally pass options to the library when obtaining the middleware
|
|---|
| 76 |
|
|---|
| 77 | ```javascript
|
|---|
| 78 | var middleware = history({});
|
|---|
| 79 | ```
|
|---|
| 80 |
|
|---|
| 81 | ### index
|
|---|
| 82 | Override the index (default `/index.html`). This is the request path that will be used when the middleware identifies that the request path needs to be rewritten.
|
|---|
| 83 |
|
|---|
| 84 | This is not the path to a file on disk. Instead it is the HTTP request path. Downstream connect/express middleware is responsible to turn this rewritten HTTP request path into actual responses, e.g. by reading a file from disk.
|
|---|
| 85 |
|
|---|
| 86 | ```javascript
|
|---|
| 87 | history({
|
|---|
| 88 | index: '/default.html'
|
|---|
| 89 | });
|
|---|
| 90 | ```
|
|---|
| 91 |
|
|---|
| 92 | ### rewrites
|
|---|
| 93 | Override the index when the request url matches a regex pattern. You can either rewrite to a static string or use a function to transform the incoming request.
|
|---|
| 94 |
|
|---|
| 95 | The following will rewrite a request that matches the `/\/soccer/` pattern to `/soccer.html`.
|
|---|
| 96 | ```javascript
|
|---|
| 97 | history({
|
|---|
| 98 | rewrites: [
|
|---|
| 99 | { from: /\/soccer/, to: '/soccer.html'}
|
|---|
| 100 | ]
|
|---|
| 101 | });
|
|---|
| 102 | ```
|
|---|
| 103 |
|
|---|
| 104 | Alternatively functions can be used to have more control over the rewrite process. For instance, the following listing shows how requests to `/libs/jquery/jquery.1.12.0.min.js` and the like can be routed to `./bower_components/libs/jquery/jquery.1.12.0.min.js`. You can also make use of this if you have an API version in the URL path.
|
|---|
| 105 | ```javascript
|
|---|
| 106 | history({
|
|---|
| 107 | rewrites: [
|
|---|
| 108 | {
|
|---|
| 109 | from: /^\/libs\/.*$/,
|
|---|
| 110 | to: function(context) {
|
|---|
| 111 | return '/bower_components' + context.parsedUrl.pathname;
|
|---|
| 112 | }
|
|---|
| 113 | }
|
|---|
| 114 | ]
|
|---|
| 115 | });
|
|---|
| 116 | ```
|
|---|
| 117 |
|
|---|
| 118 | The function will always be called with a context object that has the following properties:
|
|---|
| 119 |
|
|---|
| 120 | - **parsedUrl**: Information about the URL as provided by the [URL module's](https://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost) `url.parse`.
|
|---|
| 121 | - **match**: An Array of matched results as provided by `String.match(...)`.
|
|---|
| 122 | - **request**: The HTTP request object.
|
|---|
| 123 |
|
|---|
| 124 |
|
|---|
| 125 | ### verbose
|
|---|
| 126 | This middleware does not log any information by default. If you wish to activate logging, then you can do so via the `verbose` option or by specifying a logger function.
|
|---|
| 127 |
|
|---|
| 128 | ```javascript
|
|---|
| 129 | history({
|
|---|
| 130 | verbose: true
|
|---|
| 131 | });
|
|---|
| 132 | ```
|
|---|
| 133 |
|
|---|
| 134 | Alternatively use your own logger
|
|---|
| 135 |
|
|---|
| 136 | ```javascript
|
|---|
| 137 | history({
|
|---|
| 138 | logger: console.log.bind(console)
|
|---|
| 139 | });
|
|---|
| 140 | ```
|
|---|
| 141 |
|
|---|
| 142 | ### htmlAcceptHeaders
|
|---|
| 143 | Override the default `Accepts:` headers that are queried when matching HTML content requests (Default: `['text/html', '*/*']`).
|
|---|
| 144 |
|
|---|
| 145 | ```javascript
|
|---|
| 146 | history({
|
|---|
| 147 | htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
|---|
| 148 | })
|
|---|
| 149 | ```
|
|---|
| 150 |
|
|---|
| 151 | ### disableDotRule
|
|---|
| 152 | Disables the dot rule mentioned above:
|
|---|
| 153 |
|
|---|
| 154 | > […] is not a direct file request, i.e. the requested path does not contain a `.` (DOT) character […]
|
|---|
| 155 |
|
|---|
| 156 | ```javascript
|
|---|
| 157 | history({
|
|---|
| 158 | disableDotRule: true
|
|---|
| 159 | })
|
|---|
| 160 | ```
|
|---|