# 404 after refreshing the browser for Angular / Vue.js app

Source: https://tpiros.dev/blog/404-after-refreshing-the-browser-for-angular-vue-js-app

## Intro

You've built an Angular or Vue.js app, created a production build, and now you're serving it from your server. Everything looks fine until you navigate to a route and hit refresh. The browser spits back a 404.

Here's why that happens, and how to fix it in a Node.js / Express setup.

# Navigation strategies

Both Angular and Vue.js offer two ways to work with the browser's location history: a "hash" strategy, and one built on the `pushState` of the History API.

```
http://site.com/page --> "HTML5" style routing (using the History API)
http://site.com/#/page --> "hash" based routing
```

Both strategies follow the golden rule of SPAs (Single Page Applications): the application shell loads once, and only the content swaps out. Content lives in components, so changing the route loads the right component as defined by the router.

Modern browsers support `pushState` from the `history` object. This method lets us bolt on new entries to the browser's history and modify existing ones, all without firing a request to the server. That's the critical bit to grasp.

> The difference between the `pushState` and the hash-based strategies is that older browsers make a request to the underlying server unless a `#` is added to the URL, in which case they won't make a request.

> We _must_ use the "HTML5" style navigation (the History API) to apply server-side rendering. True for both frameworks.

## The `pushState` strategy

This strategy gives us clean, meaningful URLs that look like what users expect.

Because of `pushState()` and how the History API works, application URLs can be separated from server URLs entirely.

### Angular

Angular uses the `pushState` strategy by default, while Vue.js defaults to the "hash strategy". (Angular calls this "[PathLocationStrategy](https://angular.io/api/common/PathLocationStrategy)".)

When using this strategy, we need to specify the `<base href="/">` element in the HTML header. Alternatively, the `APP_BASE_HREF` provider token can be wired up in the module definition:

```typescript
// app.module.ts

@NgModule({
  providers: [{ provide: APP_BASE_HREF, useValue: '/' }]
})
```

### Vue.js

Vue.js doesn't use the History API by default. We need to switch it on in the routes file:

```javascript
// router.js

Vue.use(Router);

  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      component: About,
    },
  ],
});
```

Notice the `base` property. By default it gets assigned to `process.env.BASE_URL`, which points to the `publicPath` option in `vue.config.js` (if present) but falls back to `'/'`.

## "Hash" strategy

The hash strategy works by bolting a `#` symbol into the URL. It's a holdover from the fact that "older" browsers (ones that don't support the History API) fire a request to the server when they see `domain.com/path`. A `#` in the route prevents that; the page refreshes without hitting the server.

### Angular

The two frameworks differ here: Vue.js defaults to hash mode, Angular defaults to the HTML5 History API.

For Angular, the hash strategy must be set explicitly in the routing module by passing `{ useHash: true }`:

```typescript
// app-routing.module.ts
@NgModule({
  imports: [RouterModule.forRoot(routes, { useHash: true })],
  exports: [RouterModule]
})
```

### Vue.js

In Vue.js the hash strategy is the default. When we scaffold an application with routes, we'll see the `#` symbol in the URL right away.

## Handling the request in the server-side

Assume we've created a production build via `ng build --prod` for Angular or `npm run build` for Vue.js, and we want to serve it from Node.js / Express.

The boilerplate:

```javascript
const express = require('express');
const app = express();
const port = 80;
// for Angular
app.use(express.static('dist/angular-app'));
// or for Vue.js
// app.use(express.static('dist/'));
app.listen(port, () => console.info(`Server running on port ${port}`));
```

Given what we know about the History API and `pushState()`, we need to tell the server that when someone refreshes `site.com/page`, it shouldn't hunt for that page. Instead, it should serve `index.html` (the application shell, since it's a SPA) and let the router load the right component.

A global middleware handles this:

```javascript
// update for a Vue.js app accordingly
const buildLocation = 'dist/angular-app';
app.use((req, res, next) => {
  if (!req.originalUrl.includes(buildLocation)) {
    res.sendFile(`${__dirname}/${buildLocation}/index.html`);
  } else {
    next();
  }
});
```

The middleware inspects the request URL. If it doesn't match the build location (i.e. the root of the project), it sends back the application shell `index.html`, which lets the app pick up from there.

> Packages like [connect-history-api-fallback](https://github.com/bripkens/connect-history-api-fallback) can handle this too.

# Conclusion

Both Angular and Vue.js support two routing strategies: one built on the `#` symbol, one built on the History API. Using the History API (and `pushState()`) means we've got work to do on the server side to make sure a production build serves correctly on refresh.
