Skip to content

Commit

Permalink
feat: add programmatic server side redirect (#201)
Browse files Browse the repository at this point in the history
related to #195
  • Loading branch information
xMartin authored and devCrossNet committed Aug 4, 2018
1 parent 39c57ed commit ea3afe3
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ module.exports = {
'docs/style-guide',
'docs/npm-scripts',
'docs/i18n',
'docs/redirects',
'docs/storybook',
],
},
Expand Down
57 changes: 57 additions & 0 deletions docs/docs/redirects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Redirect to another page

The vue-starter includes [VueRouter](https://router.vuejs.org/) to support navigating between pages.

::: tip Server-side routing vs. client-side routing
In isomorphic apps it is important to keep in mind that routing works differently if we are handling an initial page load,
which is handled on the server side, or if we are changing the URL later and transition between views on the client side.

A redirect on the server side usually means responding with HTTP status code 301 or 302 and instead of rendering a page,
sending a different URL to the client, that it is supposed to load instead.

On the client side, a redirect is usually just automatically changing the URL.
:::

## Static route configuration

VueRouter allows setting up routes that always redirect to another route in a very simple way.

Check out the example in `src/app/app/routes.ts`:

```js
import { RouteConfig } from 'vue-router/types/router';

export const AppRoutes: RouteConfig[] = [
...
{
path: '/redirect',
redirect: '/',
},
];
```

On requesting or navigating to the path `/redirect`, the URL will be automatically updated to `/` and the home page content will be rendered.

## Programmatic redirects

The vue-starter also supports triggering a redirect depending on certain conditions, for example you want the user to see the login page
if your API responds with "Unauthorized".

In module Vue files such as `src/app/counter/Counter/Counter.vue` the `prefetch` function gets the router instance passed as an argument.
Now you can call `router.push` to change the URL before the page is rendered, which means redirecting.

Example:
```js
...
prefetch: ({ store, router }: IPreLoad) => {
return store
.dispatch('counter/increment')
// For this to work, make sure the error is not caught in the action!
.catch((error) => {
if (error.response.status === 401) {
router.push('/login');
}
});
},
...
```
2 changes: 1 addition & 1 deletion src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ router.onReady(() => {
.all(activated.map((component: Component) => {

if ((component as any).prefetch) {
return (component as any).prefetch({ store, route: to } as IPreLoad);
return (component as any).prefetch({ store, route: to, router } as IPreLoad);
}

return Promise.resolve();
Expand Down
15 changes: 11 additions & 4 deletions src/server/isomorphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Vue from 'vue';
import VueI18n from 'vue-i18n';
import { Store } from 'vuex';
import { Route } from 'vue-router';
import { Component } from 'vue-router/types/router';
import { Component, VueRouter } from 'vue-router/types/router';
import App from '../app/app/App/App.vue';
import { createApp, IApp } from '../app/app';
import { IState } from '../app/state';
Expand All @@ -25,6 +25,7 @@ export interface IServerContext {
export interface IPreLoad {
store?: Store<IState> | any;
route?: Route | any;
router?: VueRouter;
}

const setDefaultState = (context: IServerContext, store: Store<IState>) => {
Expand Down Expand Up @@ -88,16 +89,22 @@ export default (context: IServerContext) => {
Promise
.all(matchedComponents.map((component: Component) => {
if ((component as any).prefetch) {
return (component as any).prefetch({ store, route: router.currentRoute } as IPreLoad);
return (component as any).prefetch({ store, route: router.currentRoute, router } as IPreLoad);
}

return Promise.resolve();
}))
.then(() => {
context.state = store.state;

if (router.currentRoute.fullPath !== context.url) {
reject({ code: 302, path: router.currentRoute.fullPath });
// If the route from the VueRouter instance differs from the request we assume a redirect was triggered in
// the Vue application. In case only the pending route is different, `router.push` or `router.replace`
// was called, e.g. in `prefetch`.
const currentPath = router.currentRoute.fullPath;
const pendingPath = router.history.pending && router.history.pending.fullPath;

if (currentPath !== context.url || (pendingPath && pendingPath !== context.url)) {
reject({ code: 302, path: pendingPath || currentPath });
} else {
resolve(app);
}
Expand Down
12 changes: 12 additions & 0 deletions src/vue.plugins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import Vue from 'vue';
import { DefaultProps, PropsDefinition } from 'vue/types/options';
import { Route, VueRouter } from 'vue-router/types/router';

declare module 'vue/types/vue' {
interface Vue {
Expand All @@ -27,3 +28,14 @@ declare module 'vue/types/options' {
$_veeValidate?: any;
}
}

declare module 'vue-router/types/router' {
interface VueRouter {
history: {
router: VueRouter,
base: string,
current: Route,
pending?: Route,
};
}
}

0 comments on commit ea3afe3

Please sign in to comment.