├── .nuxt ├── empty.js ├── middleware.js ├── views │ ├── app.template.html │ └── error.html ├── components │ ├── nuxt-link.server.js │ ├── nuxt-error.vue │ ├── nuxt-link.client.js │ ├── nuxt.js │ ├── nuxt-child.js │ ├── nuxt-build-indicator.vue │ └── nuxt-loading.vue ├── router.js ├── loading.html ├── router.scrollBehavior.js ├── App.js ├── index.js ├── server.js ├── utils.js └── client.js ├── assets ├── logo.png └── README.md ├── static ├── favicon.ico └── README.md ├── components └── README.md ├── jsconfig.json ├── .editorconfig ├── layouts ├── README.md └── default.vue ├── pages ├── README.md └── index.vue ├── plugins ├── README.md └── aws-amplify.js ├── middleware └── README.md ├── store └── README.md ├── package.json ├── .gitignore ├── nuxt.config.js └── README.md /.nuxt/empty.js: -------------------------------------------------------------------------------- 1 | // This file is intentionally left empty for noop aliases 2 | -------------------------------------------------------------------------------- /.nuxt/middleware.js: -------------------------------------------------------------------------------- 1 | const middleware = {} 2 | 3 | export default middleware 4 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyshadowsmith/nuxt-amplify-auth-starter/HEAD/assets/logo.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyshadowsmith/nuxt-amplify-auth-starter/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /.nuxt/views/app.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ HEAD }} 5 | 6 | 7 | {{ APP }} 8 | 9 | 10 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": ["./*"], 6 | "@/*": ["./*"], 7 | "~~/*": ["./*"], 8 | "@@/*": ["./*"] 9 | } 10 | }, 11 | "exclude": ["node_modules", ".nuxt", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /plugins/aws-amplify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Amplify, * as AmplifyModules from 'aws-amplify' 3 | import { 4 | AmplifyPlugin, 5 | components 6 | } from 'aws-amplify-vue' 7 | import awsconfig from '../aws-exports' 8 | 9 | Amplify.configure(awsconfig) 10 | 11 | Vue.use(AmplifyPlugin, AmplifyModules) 12 | Vue.component(components) 13 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-link.server.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default { 4 | name: 'NuxtLink', 5 | extends: Vue.component('RouterLink'), 6 | props: { 7 | prefetch: { 8 | type: Boolean, 9 | default: true 10 | }, 11 | noPrefetch: { 12 | type: Boolean, 13 | default: false 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-amplify-auth-starter", 3 | "version": "1.0.0", 4 | "description": "Nuxt Amplify Auth Starter is a starter template for adding Amplify Auth to a Nuxt single page web application.", 5 | "author": "Shadow Smith", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate" 12 | }, 13 | "dependencies": { 14 | "aws-amplify": "^1.2.2", 15 | "aws-amplify-vue": "^0.3.2", 16 | "nuxt": "^2.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | -------------------------------------------------------------------------------- /.nuxt/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import { interopDefault } from './utils' 4 | import scrollBehavior from './router.scrollBehavior.js' 5 | 6 | const _7e11bf28 = () => interopDefault(import('..\\pages\\index.vue' /* webpackChunkName: "pages_index" */)) 7 | 8 | Vue.use(Router) 9 | 10 | export const routerOptions = { 11 | mode: 'history', 12 | base: decodeURI('/'), 13 | linkActiveClass: 'nuxt-link-active', 14 | linkExactActiveClass: 'nuxt-link-exact-active', 15 | scrollBehavior, 16 | 17 | routes: [{ 18 | path: "/", 19 | component: _7e11bf28, 20 | name: "index" 21 | }], 22 | 23 | fallback: false 24 | } 25 | 26 | export function createRouter () { 27 | return new Router(routerOptions) 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mode: "spa", 3 | /* 4 | ** Headers of the page 5 | */ 6 | head: { 7 | title: process.env.npm_package_name || "", 8 | meta: [{ 9 | charset: "utf-8" 10 | }, 11 | { 12 | name: "viewport", 13 | content: "width=device-width, initial-scale=1" 14 | }, 15 | { 16 | hid: "description", 17 | name: "description", 18 | content: process.env.npm_package_description || "" 19 | } 20 | ], 21 | link: [{ 22 | rel: "icon", 23 | type: "image/x-icon", 24 | href: "/favicon.ico" 25 | }] 26 | }, 27 | /* 28 | ** Customize the progress-bar color 29 | */ 30 | loading: { 31 | color: "#fff" 32 | }, 33 | /* 34 | ** Global CSS 35 | */ 36 | css: [], 37 | /* 38 | ** Plugins to load before mounting the App 39 | */ 40 | plugins: [{ src: "~/plugins/aws-amplify.js", mode: "client" }], 41 | /* 42 | ** Nuxt.js dev-modules 43 | */ 44 | buildModules: [], 45 | /* 46 | ** Nuxt.js modules 47 | */ 48 | modules: [], 49 | /* 50 | ** Build configuration 51 | */ 52 | build: { 53 | /* 54 | ** You can extend webpack config here 55 | */ 56 | extend(config, ctx) {} 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 47 | 48 | 64 | -------------------------------------------------------------------------------- /.nuxt/views/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server error 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |
Server error
16 |
{{ message }}
17 |
18 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /.nuxt/loading.html: -------------------------------------------------------------------------------- 1 | 97 | 98 | 106 | 107 |
Loading...
108 | 109 | 110 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-error.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 52 | 53 | 98 | -------------------------------------------------------------------------------- /.nuxt/router.scrollBehavior.js: -------------------------------------------------------------------------------- 1 | import { getMatchedComponents } from './utils' 2 | 3 | if (process.client) { 4 | if ('scrollRestoration' in window.history) { 5 | window.history.scrollRestoration = 'manual' 6 | 7 | // reset scrollRestoration to auto when leaving page, allowing page reload 8 | // and back-navigation from other pages to use the browser to restore the 9 | // scrolling position. 10 | window.addEventListener('beforeunload', () => { 11 | window.history.scrollRestoration = 'auto' 12 | }) 13 | 14 | // Setting scrollRestoration to manual again when returning to this page. 15 | window.addEventListener('load', () => { 16 | window.history.scrollRestoration = 'manual' 17 | }) 18 | } 19 | } 20 | 21 | export default function (to, from, savedPosition) { 22 | // if the returned position is falsy or an empty object, 23 | // will retain current scroll position. 24 | let position = false 25 | 26 | // if no children detected and scrollToTop is not explicitly disabled 27 | const Pages = getMatchedComponents(to) 28 | if ( 29 | Pages.length < 2 && 30 | Pages.every(Page => Page.options.scrollToTop !== false) 31 | ) { 32 | // scroll to the top of the page 33 | position = { x: 0, y: 0 } 34 | } else if (Pages.some(Page => Page.options.scrollToTop)) { 35 | // if one of the children has scrollToTop option set to true 36 | position = { x: 0, y: 0 } 37 | } 38 | 39 | // savedPosition is only available for popstate navigations (back button) 40 | if (savedPosition) { 41 | position = savedPosition 42 | } 43 | 44 | const nuxt = window.$nuxt 45 | 46 | // triggerScroll is only fired when a new component is loaded 47 | if (to.path === from.path && to.hash !== from.hash) { 48 | nuxt.$nextTick(() => nuxt.$emit('triggerScroll')) 49 | } 50 | 51 | return new Promise((resolve) => { 52 | // wait for the out transition to complete (if necessary) 53 | nuxt.$once('triggerScroll', () => { 54 | // coords will be used if no selector is provided, 55 | // or if the selector didn't match any element. 56 | if (to.hash) { 57 | let hash = to.hash 58 | // CSS.escape() is not supported with IE and Edge. 59 | if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') { 60 | hash = '#' + window.CSS.escape(hash.substr(1)) 61 | } 62 | try { 63 | if (document.querySelector(hash)) { 64 | // scroll to anchor by returning the selector 65 | position = { selector: hash } 66 | } 67 | } catch (e) { 68 | console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).') 69 | } 70 | } 71 | resolve(position) 72 | }) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-link.client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const requestIdleCallback = window.requestIdleCallback || 4 | function (cb) { 5 | const start = Date.now() 6 | return setTimeout(function () { 7 | cb({ 8 | didTimeout: false, 9 | timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) 10 | }) 11 | }, 1) 12 | } 13 | 14 | const cancelIdleCallback = window.cancelIdleCallback || function (id) { 15 | clearTimeout(id) 16 | } 17 | 18 | const observer = window.IntersectionObserver && new window.IntersectionObserver((entries) => { 19 | entries.forEach(({ intersectionRatio, target: link }) => { 20 | if (intersectionRatio <= 0) { 21 | return 22 | } 23 | link.__prefetch() 24 | }) 25 | }) 26 | 27 | export default { 28 | name: 'NuxtLink', 29 | extends: Vue.component('RouterLink'), 30 | props: { 31 | prefetch: { 32 | type: Boolean, 33 | default: true 34 | }, 35 | noPrefetch: { 36 | type: Boolean, 37 | default: false 38 | } 39 | }, 40 | mounted () { 41 | if (this.prefetch && !this.noPrefetch) { 42 | this.handleId = requestIdleCallback(this.observe, { timeout: 2e3 }) 43 | } 44 | }, 45 | beforeDestroy () { 46 | cancelIdleCallback(this.handleId) 47 | 48 | if (this.__observed) { 49 | observer.unobserve(this.$el) 50 | delete this.$el.__prefetch 51 | } 52 | }, 53 | methods: { 54 | observe () { 55 | // If no IntersectionObserver, avoid prefetching 56 | if (!observer) { 57 | return 58 | } 59 | // Add to observer 60 | if (this.shouldPrefetch()) { 61 | this.$el.__prefetch = this.prefetchLink.bind(this) 62 | observer.observe(this.$el) 63 | this.__observed = true 64 | } 65 | }, 66 | shouldPrefetch () { 67 | return this.getPrefetchComponents().length > 0 68 | }, 69 | canPrefetch () { 70 | const conn = navigator.connection 71 | const hasBadConnection = this.$nuxt.isOffline || (conn && ((conn.effectiveType || '').includes('2g') || conn.saveData)) 72 | 73 | return !hasBadConnection 74 | }, 75 | getPrefetchComponents () { 76 | const ref = this.$router.resolve(this.to, this.$route, this.append) 77 | const Components = ref.resolved.matched.map(r => r.components.default) 78 | 79 | return Components.filter(Component => typeof Component === 'function' && !Component.options && !Component.__prefetched) 80 | }, 81 | prefetchLink () { 82 | if (!this.canPrefetch()) { 83 | return 84 | } 85 | // Stop observing this link (in case of internet connection changes) 86 | observer.unobserve(this.$el) 87 | const Components = this.getPrefetchComponents() 88 | 89 | for (const Component of Components) { 90 | const componentOrPromise = Component() 91 | if (componentOrPromise instanceof Promise) { 92 | componentOrPromise.catch(() => {}) 93 | } 94 | Component.__prefetched = true 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { compile } from '../utils' 3 | 4 | import NuxtError from './nuxt-error.vue' 5 | 6 | import NuxtChild from './nuxt-child' 7 | 8 | export default { 9 | name: 'Nuxt', 10 | components: { 11 | NuxtChild, 12 | NuxtError 13 | }, 14 | props: { 15 | nuxtChildKey: { 16 | type: String, 17 | default: undefined 18 | }, 19 | keepAlive: Boolean, 20 | keepAliveProps: { 21 | type: Object, 22 | default: undefined 23 | }, 24 | name: { 25 | type: String, 26 | default: 'default' 27 | } 28 | }, 29 | errorCaptured (error) { 30 | // if we receive and error while showing the NuxtError component 31 | // capture the error and force an immediate update so we re-render 32 | // without the NuxtError component 33 | if (this.displayingNuxtError) { 34 | this.errorFromNuxtError = error 35 | this.$forceUpdate() 36 | } 37 | }, 38 | computed: { 39 | routerViewKey () { 40 | // If nuxtChildKey prop is given or current route has children 41 | if (typeof this.nuxtChildKey !== 'undefined' || this.$route.matched.length > 1) { 42 | return this.nuxtChildKey || compile(this.$route.matched[0].path)(this.$route.params) 43 | } 44 | 45 | const [matchedRoute] = this.$route.matched 46 | 47 | if (!matchedRoute) { 48 | return this.$route.path 49 | } 50 | 51 | const Component = matchedRoute.components.default 52 | 53 | if (Component && Component.options) { 54 | const { options } = Component 55 | 56 | if (options.key) { 57 | return (typeof options.key === 'function' ? options.key(this.$route) : options.key) 58 | } 59 | } 60 | 61 | const strict = /\/$/.test(matchedRoute.path) 62 | return strict ? this.$route.path : this.$route.path.replace(/\/$/, '') 63 | } 64 | }, 65 | beforeCreate () { 66 | Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt) 67 | }, 68 | render (h) { 69 | // if there is no error 70 | if (!this.nuxt.err) { 71 | // Directly return nuxt child 72 | return h('NuxtChild', { 73 | key: this.routerViewKey, 74 | props: this.$props 75 | }) 76 | } 77 | 78 | // if an error occured within NuxtError show a simple 79 | // error message instead to prevent looping 80 | if (this.errorFromNuxtError) { 81 | this.$nextTick(() => (this.errorFromNuxtError = false)) 82 | 83 | return h('div', {}, [ 84 | h('h2', 'An error occured while showing the error page'), 85 | h('p', 'Unfortunately an error occured and while showing the error page another error occured'), 86 | h('p', `Error details: ${this.errorFromNuxtError.toString()}`), 87 | h('nuxt-link', { props: { to: '/' } }, 'Go back to home') 88 | ]) 89 | } 90 | 91 | // track if we are showing the NuxtError component 92 | this.displayingNuxtError = true 93 | this.$nextTick(() => (this.displayingNuxtError = false)) 94 | 95 | return h(NuxtError, { 96 | props: { 97 | error: this.nuxt.err 98 | } 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-child.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | name: 'NuxtChild', 4 | functional: true, 5 | props: { 6 | nuxtChildKey: { 7 | type: String, 8 | default: '' 9 | }, 10 | keepAlive: Boolean, 11 | keepAliveProps: { 12 | type: Object, 13 | default: undefined 14 | } 15 | }, 16 | render (h, { parent, data, props }) { 17 | data.nuxtChild = true 18 | const _parent = parent 19 | const transitions = parent.$nuxt.nuxt.transitions 20 | const defaultTransition = parent.$nuxt.nuxt.defaultTransition 21 | 22 | let depth = 0 23 | while (parent) { 24 | if (parent.$vnode && parent.$vnode.data.nuxtChild) { 25 | depth++ 26 | } 27 | parent = parent.$parent 28 | } 29 | data.nuxtChildDepth = depth 30 | const transition = transitions[depth] || defaultTransition 31 | const transitionProps = {} 32 | transitionsKeys.forEach((key) => { 33 | if (typeof transition[key] !== 'undefined') { 34 | transitionProps[key] = transition[key] 35 | } 36 | }) 37 | 38 | const listeners = {} 39 | listenersKeys.forEach((key) => { 40 | if (typeof transition[key] === 'function') { 41 | listeners[key] = transition[key].bind(_parent) 42 | } 43 | }) 44 | // Add triggerScroll event on beforeEnter (fix #1376) 45 | const beforeEnter = listeners.beforeEnter 46 | listeners.beforeEnter = (el) => { 47 | // Ensure to trigger scroll event after calling scrollBehavior 48 | window.$nuxt.$nextTick(() => { 49 | window.$nuxt.$emit('triggerScroll') 50 | }) 51 | if (beforeEnter) { 52 | return beforeEnter.call(_parent, el) 53 | } 54 | } 55 | 56 | // make sure that leave is called asynchronous (fix #5703) 57 | if (transition.css === false) { 58 | const leave = listeners.leave 59 | 60 | // only add leave listener when user didnt provide one 61 | // or when it misses the done argument 62 | if (!leave || leave.length < 2) { 63 | listeners.leave = (el, done) => { 64 | if (leave) { 65 | leave.call(_parent, el) 66 | } 67 | 68 | _parent.$nextTick(done) 69 | } 70 | } 71 | } 72 | 73 | let routerView = h('routerView', data) 74 | 75 | if (props.keepAlive) { 76 | routerView = h('keep-alive', { props: props.keepAliveProps }, [routerView]) 77 | } 78 | 79 | return h('transition', { 80 | props: transitionProps, 81 | on: listeners 82 | }, [routerView]) 83 | } 84 | } 85 | 86 | const transitionsKeys = [ 87 | 'name', 88 | 'mode', 89 | 'appear', 90 | 'css', 91 | 'type', 92 | 'duration', 93 | 'enterClass', 94 | 'leaveClass', 95 | 'appearClass', 96 | 'enterActiveClass', 97 | 'enterActiveClass', 98 | 'leaveActiveClass', 99 | 'appearActiveClass', 100 | 'enterToClass', 101 | 'leaveToClass', 102 | 'appearToClass' 103 | ] 104 | 105 | const listenersKeys = [ 106 | 'beforeEnter', 107 | 'enter', 108 | 'afterEnter', 109 | 'enterCancelled', 110 | 'beforeLeave', 111 | 'leave', 112 | 'afterLeave', 113 | 'leaveCancelled', 114 | 'beforeAppear', 115 | 'appear', 116 | 'afterAppear', 117 | 'appearCancelled' 118 | ] 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Nuxt Amplify Auth Starter](https://github.com/TheShadowSmith/nuxt-amplify-auth-starter/blob/master/assets/logo.png "Nuxt Amplify Auth Starter") 2 | 3 | # About Nuxt Amplify Auth Starter 4 | Nuxt Amplify Auth Starter is a starter template for adding Amplify Auth to a [Nuxt.js](https://nuxtjs.org/) single page web application. All you have to do is connect your Amazon Web Services account to it by using the Amplify CLI, and you will be able to add [Amazon Cognito Authentication](https://aws.amazon.com/cognito/) to your app in minutes. 5 | 6 | ## Install and configure the Amplify CLI 7 | You must have the Amplify CLI installed on your machine to continue with this installation. 8 | In order to do this, [head on over to the Amplify docs](https://aws-amplify.github.io/docs/). 9 | 10 | ## Add Amplify to your project 11 | 1. Run `amplify init` in your terminal. 12 | 2. When prompted *"Enter a name for the project"*, name it whatever you would like. 13 | 3. When prompted *"Enter a name for the environment"*, I recommend naming it **dev**, but this is also up to you. 14 | 4. When prompted *"Choose your default code editor"*, choose the code editor you use from the list. 15 | 5. When prompted *"Choose the type of app that you're building"*, choose **javascript**. 16 | 6. When prompted *"What javascript framework are you using"*, choose **vue**. 17 | 7. When prompted *"Source Directory Path:"*, choose **.** (just a period) since the default Nuxt.js structure doesn't use a **src** directory. 18 | 8. When prompted *"Distribution Directory Path:"*, choose the default **dist**. 19 | 9. When prompted *"Build Command:"*, choose the default `npm.cmd run-script build`. 20 | 10. When prompted *"Start Command:"*, enter `npm.cmd run-script start`. 21 | 11. When prompted *"Do you want to use an AWS profile?"*, choose **Y** and select the profile you want to use. 22 | 23 | ## Add Authentication to your project 24 | 1. Run `amplify add auth` in your terminal. 25 | 2. When prompted *"Do you want to use the default authentication and security configuration?"*, choose **Default configuration**. 26 | 3. When prompted *"How do you want users to be able to sign in?"*, choose **Username**. 27 | 4. When prompted *"Do you want to configure advanced settings?"*, choose **No, I am done**. 28 | 29 | ## Add Hosting for your project 30 | 1. Run `amplify add hosting` in your terminal. 31 | 2. When prompted *Select the environment setup*, choose **DEV (S3 only with HTTP)**. 32 | The difference between DEV and PROD in this situation is what all resources AWS provisions for your app when you publish it. 33 | DEV only uses an S3 for hosting your static files; whereas, PROD uses S3 for hosting your static files, CloudFront to deliver your static files through a CDN, and HTTPS. 34 | 3. When prompted *hosting bucket name*, name it whatever you would like or choose the default. 35 | 4. When prompted *index doc for the website*, choose the default. 36 | 5. When prompted *error doc for the website*, choose the default. 37 | 38 | ## Provisioning your Authentication and Hosting backend services 39 | 1. Run `amplify push` in your terminal. 40 | 2. When prompted *"Are you sure you want to continue?"*, choose **Y**. 41 | List step takes a few minutes to complete. 42 | 43 | ## Launch the app for development 44 | 1. Run `npm run dev` in your terminal. 45 | 46 | ## Deploy the app to Hosting 47 | 1. Run `amplify publish` to auto-build your SPA and upload it to your S3 bucket. -------------------------------------------------------------------------------- /.nuxt/components/nuxt-build-indicator.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 112 | 113 | 143 | -------------------------------------------------------------------------------- /.nuxt/components/nuxt-loading.vue: -------------------------------------------------------------------------------- 1 | 155 | 156 | 178 | -------------------------------------------------------------------------------- /.nuxt/App.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import { 4 | getMatchedComponentsInstances, 5 | promisify, 6 | globalHandleError 7 | } from './utils' 8 | 9 | import NuxtLoading from './components/nuxt-loading.vue' 10 | import NuxtBuildIndicator from './components/nuxt-build-indicator' 11 | 12 | import _6f6c098b from '..\\layouts\\default.vue' 13 | 14 | const layouts = { "_default": _6f6c098b } 15 | 16 | export default { 17 | head: {"title":"nuxt-amplify-auth-starter","meta":[{"charset":"utf-8"},{"name":"viewport","content":"width=device-width, initial-scale=1"},{"hid":"description","name":"description","content":"This project shows how to add Amplify Auth to a Nuxt app."}],"link":[{"rel":"icon","type":"image\u002Fx-icon","href":"\u002Ffavicon.ico"}],"style":[],"script":[]}, 18 | 19 | render (h, props) { 20 | const loadingEl = h('NuxtLoading', { ref: 'loading' }) 21 | 22 | const layoutEl = h(this.layout || 'nuxt') 23 | const templateEl = h('div', { 24 | domProps: { 25 | id: '__layout' 26 | }, 27 | key: this.layoutName 28 | }, [ layoutEl ]) 29 | 30 | const transitionEl = h('transition', { 31 | props: { 32 | name: 'layout', 33 | mode: 'out-in' 34 | }, 35 | on: { 36 | beforeEnter (el) { 37 | // Ensure to trigger scroll event after calling scrollBehavior 38 | window.$nuxt.$nextTick(() => { 39 | window.$nuxt.$emit('triggerScroll') 40 | }) 41 | } 42 | } 43 | }, [ templateEl ]) 44 | 45 | return h('div', { 46 | domProps: { 47 | id: '__nuxt' 48 | } 49 | }, [ 50 | loadingEl, 51 | h(NuxtBuildIndicator), 52 | transitionEl 53 | ]) 54 | }, 55 | 56 | data: () => ({ 57 | isOnline: true, 58 | 59 | layout: null, 60 | layoutName: '' 61 | }), 62 | 63 | beforeCreate () { 64 | Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt) 65 | }, 66 | created () { 67 | // Add this.$nuxt in child instances 68 | Vue.prototype.$nuxt = this 69 | // add to window so we can listen when ready 70 | if (process.client) { 71 | window.$nuxt = this 72 | 73 | this.refreshOnlineStatus() 74 | // Setup the listeners 75 | window.addEventListener('online', this.refreshOnlineStatus) 76 | window.addEventListener('offline', this.refreshOnlineStatus) 77 | } 78 | // Add $nuxt.error() 79 | this.error = this.nuxt.error 80 | // Add $nuxt.context 81 | this.context = this.$options.context 82 | }, 83 | 84 | mounted () { 85 | this.$loading = this.$refs.loading 86 | }, 87 | watch: { 88 | 'nuxt.err': 'errorChanged' 89 | }, 90 | 91 | computed: { 92 | isOffline () { 93 | return !this.isOnline 94 | } 95 | }, 96 | 97 | methods: { 98 | refreshOnlineStatus () { 99 | if (process.client) { 100 | if (typeof window.navigator.onLine === 'undefined') { 101 | // If the browser doesn't support connection status reports 102 | // assume that we are online because most apps' only react 103 | // when they now that the connection has been interrupted 104 | this.isOnline = true 105 | } else { 106 | this.isOnline = window.navigator.onLine 107 | } 108 | } 109 | }, 110 | 111 | async refresh () { 112 | const pages = getMatchedComponentsInstances(this.$route) 113 | 114 | if (!pages.length) { 115 | return 116 | } 117 | this.$loading.start() 118 | 119 | const promises = pages.map((page) => { 120 | const p = [] 121 | 122 | if (page.$options.fetch) { 123 | p.push(promisify(page.$options.fetch, this.context)) 124 | } 125 | 126 | if (page.$options.asyncData) { 127 | p.push( 128 | promisify(page.$options.asyncData, this.context) 129 | .then((newData) => { 130 | for (const key in newData) { 131 | Vue.set(page.$data, key, newData[key]) 132 | } 133 | }) 134 | ) 135 | } 136 | 137 | return Promise.all(p) 138 | }) 139 | try { 140 | await Promise.all(promises) 141 | } catch (error) { 142 | this.$loading.fail() 143 | globalHandleError(error) 144 | this.error(error) 145 | } 146 | this.$loading.finish() 147 | }, 148 | 149 | errorChanged () { 150 | if (this.nuxt.err && this.$loading) { 151 | if (this.$loading.fail) { 152 | this.$loading.fail() 153 | } 154 | if (this.$loading.finish) { 155 | this.$loading.finish() 156 | } 157 | } 158 | }, 159 | 160 | setLayout (layout) { 161 | if(layout && typeof layout !== 'string') { 162 | throw new Error('[nuxt] Avoid using non-string value as layout property.') 163 | } 164 | 165 | if (!layout || !layouts['_' + layout]) { 166 | layout = 'default' 167 | } 168 | this.layoutName = layout 169 | this.layout = layouts['_' + layout] 170 | return this.layout 171 | }, 172 | loadLayout (layout) { 173 | if (!layout || !layouts['_' + layout]) { 174 | layout = 'default' 175 | } 176 | return Promise.resolve(layouts['_' + layout]) 177 | } 178 | }, 179 | 180 | components: { 181 | NuxtLoading 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /.nuxt/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Meta from 'vue-meta' 3 | import ClientOnly from 'vue-client-only' 4 | import NoSsr from 'vue-no-ssr' 5 | import { createRouter } from './router.js' 6 | import NuxtChild from './components/nuxt-child.js' 7 | import NuxtError from './components/nuxt-error.vue' 8 | import Nuxt from './components/nuxt.js' 9 | import App from './App.js' 10 | import { setContext, getLocation, getRouteData, normalizeError } from './utils' 11 | 12 | /* Plugins */ 13 | 14 | import nuxt_plugin_awsamplify_8f02af2e from 'nuxt_plugin_awsamplify_8f02af2e' // Source: ..\\plugins\\aws-amplify.js (mode: 'client') 15 | 16 | // Component: 17 | Vue.component(ClientOnly.name, ClientOnly) 18 | 19 | // TODO: Remove in Nuxt 3: 20 | Vue.component(NoSsr.name, { 21 | ...NoSsr, 22 | render (h, ctx) { 23 | if (process.client && !NoSsr._warned) { 24 | NoSsr._warned = true 25 | 26 | console.warn(` has been deprecated and will be removed in Nuxt 3, please use instead`) 27 | } 28 | return NoSsr.render(h, ctx) 29 | } 30 | }) 31 | 32 | // Component: 33 | Vue.component(NuxtChild.name, NuxtChild) 34 | Vue.component('NChild', NuxtChild) 35 | 36 | // Component NuxtLink is imported in server.js or client.js 37 | 38 | // Component: ` 39 | Vue.component(Nuxt.name, Nuxt) 40 | 41 | Vue.use(Meta, {"keyName":"head","attribute":"data-n-head","ssrAttribute":"data-n-head-ssr","tagIDKeyName":"hid"}) 42 | 43 | const defaultTransition = {"name":"page","mode":"out-in","appear":true,"appearClass":"appear","appearActiveClass":"appear-active","appearToClass":"appear-to"} 44 | 45 | async function createApp (ssrContext) { 46 | const router = await createRouter(ssrContext) 47 | 48 | // Create Root instance 49 | 50 | // here we inject the router and store to all child components, 51 | // making them available everywhere as `this.$router` and `this.$store`. 52 | const app = { 53 | router, 54 | nuxt: { 55 | defaultTransition, 56 | transitions: [ defaultTransition ], 57 | setTransitions (transitions) { 58 | if (!Array.isArray(transitions)) { 59 | transitions = [ transitions ] 60 | } 61 | transitions = transitions.map((transition) => { 62 | if (!transition) { 63 | transition = defaultTransition 64 | } else if (typeof transition === 'string') { 65 | transition = Object.assign({}, defaultTransition, { name: transition }) 66 | } else { 67 | transition = Object.assign({}, defaultTransition, transition) 68 | } 69 | return transition 70 | }) 71 | this.$options.nuxt.transitions = transitions 72 | return transitions 73 | }, 74 | 75 | err: null, 76 | dateErr: null, 77 | error (err) { 78 | err = err || null 79 | app.context._errored = Boolean(err) 80 | err = err ? normalizeError(err) : null 81 | const nuxt = this.nuxt || this.$options.nuxt 82 | nuxt.dateErr = Date.now() 83 | nuxt.err = err 84 | // Used in src/server.js 85 | if (ssrContext) { 86 | ssrContext.nuxt.error = err 87 | } 88 | return err 89 | } 90 | }, 91 | ...App 92 | } 93 | 94 | const next = ssrContext ? ssrContext.next : location => app.router.push(location) 95 | // Resolve route 96 | let route 97 | if (ssrContext) { 98 | route = router.resolve(ssrContext.url).route 99 | } else { 100 | const path = getLocation(router.options.base) 101 | route = router.resolve(path).route 102 | } 103 | 104 | // Set context to app.context 105 | await setContext(app, { 106 | route, 107 | next, 108 | error: app.nuxt.error.bind(app), 109 | payload: ssrContext ? ssrContext.payload : undefined, 110 | req: ssrContext ? ssrContext.req : undefined, 111 | res: ssrContext ? ssrContext.res : undefined, 112 | beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined, 113 | ssrContext 114 | }) 115 | 116 | const inject = function (key, value) { 117 | if (!key) { 118 | throw new Error('inject(key, value) has no key provided') 119 | } 120 | if (value === undefined) { 121 | throw new Error('inject(key, value) has no value provided') 122 | } 123 | 124 | key = '$' + key 125 | // Add into app 126 | app[key] = value 127 | 128 | // Check if plugin not already installed 129 | const installKey = '__nuxt_' + key + '_installed__' 130 | if (Vue[installKey]) { 131 | return 132 | } 133 | Vue[installKey] = true 134 | // Call Vue.use() to install the plugin into vm 135 | Vue.use(() => { 136 | if (!Vue.prototype.hasOwnProperty(key)) { 137 | Object.defineProperty(Vue.prototype, key, { 138 | get () { 139 | return this.$root.$options[key] 140 | } 141 | }) 142 | } 143 | }) 144 | } 145 | 146 | // Plugin execution 147 | 148 | if (process.client && typeof nuxt_plugin_awsamplify_8f02af2e === 'function') { 149 | await nuxt_plugin_awsamplify_8f02af2e(app.context, inject) 150 | } 151 | 152 | // If server-side, wait for async component to be resolved first 153 | if (process.server && ssrContext && ssrContext.url) { 154 | await new Promise((resolve, reject) => { 155 | router.push(ssrContext.url, resolve, () => { 156 | // navigated to a different route in router guard 157 | const unregister = router.afterEach(async (to, from, next) => { 158 | ssrContext.url = to.fullPath 159 | app.context.route = await getRouteData(to) 160 | app.context.params = to.params || {} 161 | app.context.query = to.query || {} 162 | unregister() 163 | resolve() 164 | }) 165 | }) 166 | }) 167 | } 168 | 169 | return { 170 | app, 171 | router 172 | } 173 | } 174 | 175 | export { createApp, NuxtError } 176 | -------------------------------------------------------------------------------- /.nuxt/server.js: -------------------------------------------------------------------------------- 1 | import { stringify } from 'querystring' 2 | import Vue from 'vue' 3 | import fetch from 'node-fetch' 4 | import middleware from './middleware.js' 5 | import { 6 | applyAsyncData, 7 | middlewareSeries, 8 | sanitizeComponent, 9 | getMatchedComponents, 10 | promisify 11 | } from './utils.js' 12 | import { createApp, NuxtError } from './index.js' 13 | import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js 14 | 15 | // Component: 16 | Vue.component(NuxtLink.name, NuxtLink) 17 | Vue.component('NLink', NuxtLink) 18 | 19 | if (!global.fetch) { global.fetch = fetch } 20 | 21 | const noopApp = () => new Vue({ render: h => h('div') }) 22 | 23 | function urlJoin () { 24 | return Array.prototype.slice.call(arguments).join('/').replace(/\/+/g, '/') 25 | } 26 | 27 | const createNext = ssrContext => (opts) => { 28 | ssrContext.redirected = opts 29 | // If nuxt generate 30 | if (!ssrContext.res) { 31 | ssrContext.nuxt.serverRendered = false 32 | return 33 | } 34 | opts.query = stringify(opts.query) 35 | opts.path = opts.path + (opts.query ? '?' + opts.query : '') 36 | const routerBase = '/' 37 | if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) { 38 | opts.path = urlJoin(routerBase, opts.path) 39 | } 40 | // Avoid loop redirect 41 | if (opts.path === ssrContext.url) { 42 | ssrContext.redirected = false 43 | return 44 | } 45 | ssrContext.res.writeHead(opts.status, { 46 | 'Location': opts.path 47 | }) 48 | ssrContext.res.end() 49 | } 50 | 51 | // This exported function will be called by `bundleRenderer`. 52 | // This is where we perform data-prefetching to determine the 53 | // state of our application before actually rendering it. 54 | // Since data fetching is async, this function is expected to 55 | // return a Promise that resolves to the app instance. 56 | export default async (ssrContext) => { 57 | // Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect 58 | ssrContext.redirected = false 59 | ssrContext.next = createNext(ssrContext) 60 | // Used for beforeNuxtRender({ Components, nuxtState }) 61 | ssrContext.beforeRenderFns = [] 62 | // Nuxt object (window{{globals.context}}, defaults to window.__NUXT__) 63 | ssrContext.nuxt = { layout: 'default', data: [], error: null, serverRendered: true } 64 | // Create the app definition and the instance (created for each request) 65 | const { app, router } = await createApp(ssrContext) 66 | const _app = new Vue(app) 67 | 68 | // Add meta infos (used in renderer.js) 69 | ssrContext.meta = _app.$meta() 70 | 71 | // Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext) 72 | ssrContext.asyncData = {} 73 | 74 | const beforeRender = async () => { 75 | // Call beforeNuxtRender() methods 76 | await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt }))) 77 | } 78 | 79 | const renderErrorPage = async () => { 80 | // Load layout for error page 81 | const errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout) 82 | ssrContext.nuxt.layout = errLayout || 'default' 83 | await _app.loadLayout(errLayout) 84 | _app.setLayout(errLayout) 85 | 86 | await beforeRender() 87 | return _app 88 | } 89 | const render404Page = () => { 90 | app.context.error({ statusCode: 404, path: ssrContext.url, message: `This page could not be found` }) 91 | return renderErrorPage() 92 | } 93 | 94 | const s = Date.now() 95 | 96 | // Components are already resolved by setContext -> getRouteData (app/utils.js) 97 | const Components = getMatchedComponents(router.match(ssrContext.url)) 98 | 99 | /* 100 | ** Call global middleware (nuxt.config.js) 101 | */ 102 | let midd = [] 103 | midd = midd.map((name) => { 104 | if (typeof name === 'function') { 105 | return name 106 | } 107 | if (typeof middleware[name] !== 'function') { 108 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 109 | } 110 | return middleware[name] 111 | }) 112 | await middlewareSeries(midd, app.context) 113 | // ...If there is a redirect or an error, stop the process 114 | if (ssrContext.redirected) { 115 | return noopApp() 116 | } 117 | if (ssrContext.nuxt.error) { 118 | return renderErrorPage() 119 | } 120 | 121 | /* 122 | ** Set layout 123 | */ 124 | let layout = Components.length ? Components[0].options.layout : NuxtError.layout 125 | if (typeof layout === 'function') { 126 | layout = layout(app.context) 127 | } 128 | await _app.loadLayout(layout) 129 | if (ssrContext.nuxt.error) { 130 | return renderErrorPage() 131 | } 132 | layout = _app.setLayout(layout) 133 | ssrContext.nuxt.layout = _app.layoutName 134 | 135 | /* 136 | ** Call middleware (layout + pages) 137 | */ 138 | midd = [] 139 | 140 | layout = sanitizeComponent(layout) 141 | if (layout.options.middleware) { 142 | midd = midd.concat(layout.options.middleware) 143 | } 144 | 145 | Components.forEach((Component) => { 146 | if (Component.options.middleware) { 147 | midd = midd.concat(Component.options.middleware) 148 | } 149 | }) 150 | midd = midd.map((name) => { 151 | if (typeof name === 'function') { 152 | return name 153 | } 154 | if (typeof middleware[name] !== 'function') { 155 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 156 | } 157 | return middleware[name] 158 | }) 159 | await middlewareSeries(midd, app.context) 160 | // ...If there is a redirect or an error, stop the process 161 | if (ssrContext.redirected) { 162 | return noopApp() 163 | } 164 | if (ssrContext.nuxt.error) { 165 | return renderErrorPage() 166 | } 167 | 168 | /* 169 | ** Call .validate() 170 | */ 171 | let isValid = true 172 | try { 173 | for (const Component of Components) { 174 | if (typeof Component.options.validate !== 'function') { 175 | continue 176 | } 177 | 178 | isValid = await Component.options.validate(app.context) 179 | 180 | if (!isValid) { 181 | break 182 | } 183 | } 184 | } catch (validationError) { 185 | // ...If .validate() threw an error 186 | app.context.error({ 187 | statusCode: validationError.statusCode || '500', 188 | message: validationError.message 189 | }) 190 | return renderErrorPage() 191 | } 192 | 193 | // ...If .validate() returned false 194 | if (!isValid) { 195 | // Don't server-render the page in generate mode 196 | if (ssrContext._generate) { 197 | ssrContext.nuxt.serverRendered = false 198 | } 199 | // Render a 404 error page 200 | return render404Page() 201 | } 202 | 203 | // If no Components found, returns 404 204 | if (!Components.length) { 205 | return render404Page() 206 | } 207 | 208 | // Call asyncData & fetch hooks on components matched by the route. 209 | const asyncDatas = await Promise.all(Components.map((Component) => { 210 | const promises = [] 211 | 212 | // Call asyncData(context) 213 | if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { 214 | const promise = promisify(Component.options.asyncData, app.context) 215 | promise.then((asyncDataResult) => { 216 | ssrContext.asyncData[Component.cid] = asyncDataResult 217 | applyAsyncData(Component) 218 | return asyncDataResult 219 | }) 220 | promises.push(promise) 221 | } else { 222 | promises.push(null) 223 | } 224 | 225 | // Call fetch(context) 226 | if (Component.options.fetch) { 227 | promises.push(Component.options.fetch(app.context)) 228 | } else { 229 | promises.push(null) 230 | } 231 | 232 | return Promise.all(promises) 233 | })) 234 | 235 | if (process.env.DEBUG && asyncDatas.length) console.debug('Data fetching ' + ssrContext.url + ': ' + (Date.now() - s) + 'ms') 236 | 237 | // datas are the first row of each 238 | ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {}) 239 | 240 | // ...If there is a redirect or an error, stop the process 241 | if (ssrContext.redirected) { 242 | return noopApp() 243 | } 244 | if (ssrContext.nuxt.error) { 245 | return renderErrorPage() 246 | } 247 | 248 | // Call beforeNuxtRender methods & add store state 249 | await beforeRender() 250 | 251 | return _app 252 | } 253 | -------------------------------------------------------------------------------- /.nuxt/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // window.{{globals.loadedCallback}} hook 4 | // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) 5 | if (process.client) { 6 | window.onNuxtReadyCbs = [] 7 | window.onNuxtReady = (cb) => { 8 | window.onNuxtReadyCbs.push(cb) 9 | } 10 | } 11 | 12 | export function empty () {} 13 | 14 | export function globalHandleError (error) { 15 | if (Vue.config.errorHandler) { 16 | Vue.config.errorHandler(error) 17 | } 18 | } 19 | 20 | export function interopDefault (promise) { 21 | return promise.then(m => m.default || m) 22 | } 23 | 24 | export function applyAsyncData (Component, asyncData) { 25 | if ( 26 | // For SSR, we once all this function without second param to just apply asyncData 27 | // Prevent doing this for each SSR request 28 | !asyncData && Component.options.__hasNuxtData 29 | ) { 30 | return 31 | } 32 | 33 | const ComponentData = Component.options._originDataFn || Component.options.data || function () { return {} } 34 | Component.options._originDataFn = ComponentData 35 | 36 | Component.options.data = function () { 37 | const data = ComponentData.call(this, this) 38 | if (this.$ssrContext) { 39 | asyncData = this.$ssrContext.asyncData[Component.cid] 40 | } 41 | return { ...data, ...asyncData } 42 | } 43 | 44 | Component.options.__hasNuxtData = true 45 | 46 | if (Component._Ctor && Component._Ctor.options) { 47 | Component._Ctor.options.data = Component.options.data 48 | } 49 | } 50 | 51 | export function sanitizeComponent (Component) { 52 | // If Component already sanitized 53 | if (Component.options && Component._Ctor === Component) { 54 | return Component 55 | } 56 | if (!Component.options) { 57 | Component = Vue.extend(Component) // fix issue #6 58 | Component._Ctor = Component 59 | } else { 60 | Component._Ctor = Component 61 | Component.extendOptions = Component.options 62 | } 63 | // For debugging purpose 64 | if (!Component.options.name && Component.options.__file) { 65 | Component.options.name = Component.options.__file 66 | } 67 | return Component 68 | } 69 | 70 | export function getMatchedComponents (route, matches = false, prop = 'components') { 71 | return Array.prototype.concat.apply([], route.matched.map((m, index) => { 72 | return Object.keys(m[prop]).map((key) => { 73 | matches && matches.push(index) 74 | return m[prop][key] 75 | }) 76 | })) 77 | } 78 | 79 | export function getMatchedComponentsInstances (route, matches = false) { 80 | return getMatchedComponents(route, matches, 'instances') 81 | } 82 | 83 | export function flatMapComponents (route, fn) { 84 | return Array.prototype.concat.apply([], route.matched.map((m, index) => { 85 | return Object.keys(m.components).reduce((promises, key) => { 86 | if (m.components[key]) { 87 | promises.push(fn(m.components[key], m.instances[key], m, key, index)) 88 | } else { 89 | delete m.components[key] 90 | } 91 | return promises 92 | }, []) 93 | })) 94 | } 95 | 96 | export function resolveRouteComponents (route, fn) { 97 | return Promise.all( 98 | flatMapComponents(route, async (Component, instance, match, key) => { 99 | // If component is a function, resolve it 100 | if (typeof Component === 'function' && !Component.options) { 101 | Component = await Component() 102 | } 103 | match.components[key] = Component = sanitizeComponent(Component) 104 | return typeof fn === 'function' ? fn(Component, instance, match, key) : Component 105 | }) 106 | ) 107 | } 108 | 109 | export async function getRouteData (route) { 110 | if (!route) { 111 | return 112 | } 113 | // Make sure the components are resolved (code-splitting) 114 | await resolveRouteComponents(route) 115 | // Send back a copy of route with meta based on Component definition 116 | return { 117 | ...route, 118 | meta: getMatchedComponents(route).map((Component, index) => { 119 | return { ...Component.options.meta, ...(route.matched[index] || {}).meta } 120 | }) 121 | } 122 | } 123 | 124 | export async function setContext (app, context) { 125 | // If context not defined, create it 126 | if (!app.context) { 127 | app.context = { 128 | isStatic: process.static, 129 | isDev: true, 130 | isHMR: false, 131 | app, 132 | 133 | payload: context.payload, 134 | error: context.error, 135 | base: '/', 136 | env: {} 137 | } 138 | // Only set once 139 | if (context.req) { 140 | app.context.req = context.req 141 | } 142 | if (context.res) { 143 | app.context.res = context.res 144 | } 145 | if (context.ssrContext) { 146 | app.context.ssrContext = context.ssrContext 147 | } 148 | app.context.redirect = (status, path, query) => { 149 | if (!status) { 150 | return 151 | } 152 | app.context._redirected = true 153 | // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' }) 154 | let pathType = typeof path 155 | if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) { 156 | query = path || {} 157 | path = status 158 | pathType = typeof path 159 | status = 302 160 | } 161 | if (pathType === 'object') { 162 | path = app.router.resolve(path).route.fullPath 163 | } 164 | // "/absolute/route", "./relative/route" or "../relative/route" 165 | if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) { 166 | app.context.next({ 167 | path, 168 | query, 169 | status 170 | }) 171 | } else { 172 | path = formatUrl(path, query) 173 | if (process.server) { 174 | app.context.next({ 175 | path, 176 | status 177 | }) 178 | } 179 | if (process.client) { 180 | // https://developer.mozilla.org/en-US/docs/Web/API/Location/replace 181 | window.location.replace(path) 182 | 183 | // Throw a redirect error 184 | throw new Error('ERR_REDIRECT') 185 | } 186 | } 187 | } 188 | if (process.server) { 189 | app.context.beforeNuxtRender = fn => context.beforeRenderFns.push(fn) 190 | } 191 | if (process.client) { 192 | app.context.nuxtState = window.__NUXT__ 193 | } 194 | } 195 | 196 | // Dynamic keys 197 | const [currentRouteData, fromRouteData] = await Promise.all([ 198 | getRouteData(context.route), 199 | getRouteData(context.from) 200 | ]) 201 | 202 | if (context.route) { 203 | app.context.route = currentRouteData 204 | } 205 | 206 | if (context.from) { 207 | app.context.from = fromRouteData 208 | } 209 | 210 | app.context.next = context.next 211 | app.context._redirected = false 212 | app.context._errored = false 213 | app.context.isHMR = Boolean(context.isHMR) 214 | app.context.params = app.context.route.params || {} 215 | app.context.query = app.context.route.query || {} 216 | } 217 | 218 | export function middlewareSeries (promises, appContext) { 219 | if (!promises.length || appContext._redirected || appContext._errored) { 220 | return Promise.resolve() 221 | } 222 | return promisify(promises[0], appContext) 223 | .then(() => { 224 | return middlewareSeries(promises.slice(1), appContext) 225 | }) 226 | } 227 | 228 | export function promisify (fn, context) { 229 | let promise 230 | if (fn.length === 2) { 231 | console.warn('Callback-based asyncData, fetch or middleware calls are deprecated. ' + 232 | 'Please switch to promises or async/await syntax') 233 | 234 | // fn(context, callback) 235 | promise = new Promise((resolve) => { 236 | fn(context, function (err, data) { 237 | if (err) { 238 | context.error(err) 239 | } 240 | data = data || {} 241 | resolve(data) 242 | }) 243 | }) 244 | } else { 245 | promise = fn(context) 246 | } 247 | 248 | if (promise && promise instanceof Promise && typeof promise.then === 'function') { 249 | return promise 250 | } 251 | return Promise.resolve(promise) 252 | } 253 | 254 | // Imported from vue-router 255 | export function getLocation (base, mode) { 256 | let path = decodeURI(window.location.pathname) 257 | if (mode === 'hash') { 258 | return window.location.hash.replace(/^#\//, '') 259 | } 260 | if (base && path.indexOf(base) === 0) { 261 | path = path.slice(base.length) 262 | } 263 | return (path || '/') + window.location.search + window.location.hash 264 | } 265 | 266 | // Imported from path-to-regexp 267 | 268 | /** 269 | * Compile a string to a template function for the path. 270 | * 271 | * @param {string} str 272 | * @param {Object=} options 273 | * @return {!function(Object=, Object=)} 274 | */ 275 | export function compile (str, options) { 276 | return tokensToFunction(parse(str, options)) 277 | } 278 | 279 | export function getQueryDiff (toQuery, fromQuery) { 280 | const diff = {} 281 | const queries = { ...toQuery, ...fromQuery } 282 | for (const k in queries) { 283 | if (String(toQuery[k]) !== String(fromQuery[k])) { 284 | diff[k] = true 285 | } 286 | } 287 | return diff 288 | } 289 | 290 | export function normalizeError (err) { 291 | let message 292 | if (!(err.message || typeof err === 'string')) { 293 | try { 294 | message = JSON.stringify(err, null, 2) 295 | } catch (e) { 296 | message = `[${err.constructor.name}]` 297 | } 298 | } else { 299 | message = err.message || err 300 | } 301 | return { 302 | ...err, 303 | message, 304 | statusCode: (err.statusCode || err.status || (err.response && err.response.status) || 500) 305 | } 306 | } 307 | 308 | /** 309 | * The main path matching regexp utility. 310 | * 311 | * @type {RegExp} 312 | */ 313 | const PATH_REGEXP = new RegExp([ 314 | // Match escaped characters that would otherwise appear in future matches. 315 | // This allows the user to escape special characters that won't transform. 316 | '(\\\\.)', 317 | // Match Express-style parameters and un-named parameters with a prefix 318 | // and optional suffixes. Matches appear as: 319 | // 320 | // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] 321 | // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] 322 | // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] 323 | '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' 324 | ].join('|'), 'g') 325 | 326 | /** 327 | * Parse a string for the raw tokens. 328 | * 329 | * @param {string} str 330 | * @param {Object=} options 331 | * @return {!Array} 332 | */ 333 | function parse (str, options) { 334 | const tokens = [] 335 | let key = 0 336 | let index = 0 337 | let path = '' 338 | const defaultDelimiter = (options && options.delimiter) || '/' 339 | let res 340 | 341 | while ((res = PATH_REGEXP.exec(str)) != null) { 342 | const m = res[0] 343 | const escaped = res[1] 344 | const offset = res.index 345 | path += str.slice(index, offset) 346 | index = offset + m.length 347 | 348 | // Ignore already escaped sequences. 349 | if (escaped) { 350 | path += escaped[1] 351 | continue 352 | } 353 | 354 | const next = str[index] 355 | const prefix = res[2] 356 | const name = res[3] 357 | const capture = res[4] 358 | const group = res[5] 359 | const modifier = res[6] 360 | const asterisk = res[7] 361 | 362 | // Push the current path onto the tokens. 363 | if (path) { 364 | tokens.push(path) 365 | path = '' 366 | } 367 | 368 | const partial = prefix != null && next != null && next !== prefix 369 | const repeat = modifier === '+' || modifier === '*' 370 | const optional = modifier === '?' || modifier === '*' 371 | const delimiter = res[2] || defaultDelimiter 372 | const pattern = capture || group 373 | 374 | tokens.push({ 375 | name: name || key++, 376 | prefix: prefix || '', 377 | delimiter, 378 | optional, 379 | repeat, 380 | partial, 381 | asterisk: Boolean(asterisk), 382 | pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') 383 | }) 384 | } 385 | 386 | // Match any characters still remaining. 387 | if (index < str.length) { 388 | path += str.substr(index) 389 | } 390 | 391 | // If the path exists, push it onto the end. 392 | if (path) { 393 | tokens.push(path) 394 | } 395 | 396 | return tokens 397 | } 398 | 399 | /** 400 | * Prettier encoding of URI path segments. 401 | * 402 | * @param {string} 403 | * @return {string} 404 | */ 405 | function encodeURIComponentPretty (str, slashAllowed) { 406 | const re = slashAllowed ? /[?#]/g : /[/?#]/g 407 | return encodeURI(str).replace(re, (c) => { 408 | return '%' + c.charCodeAt(0).toString(16).toUpperCase() 409 | }) 410 | } 411 | 412 | /** 413 | * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. 414 | * 415 | * @param {string} 416 | * @return {string} 417 | */ 418 | function encodeAsterisk (str) { 419 | return encodeURIComponentPretty(str, true) 420 | } 421 | 422 | /** 423 | * Escape a regular expression string. 424 | * 425 | * @param {string} str 426 | * @return {string} 427 | */ 428 | function escapeString (str) { 429 | return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') 430 | } 431 | 432 | /** 433 | * Escape the capturing group by escaping special characters and meaning. 434 | * 435 | * @param {string} group 436 | * @return {string} 437 | */ 438 | function escapeGroup (group) { 439 | return group.replace(/([=!:$/()])/g, '\\$1') 440 | } 441 | 442 | /** 443 | * Expose a method for transforming tokens into the path function. 444 | */ 445 | function tokensToFunction (tokens) { 446 | // Compile all the tokens into regexps. 447 | const matches = new Array(tokens.length) 448 | 449 | // Compile all the patterns before compilation. 450 | for (let i = 0; i < tokens.length; i++) { 451 | if (typeof tokens[i] === 'object') { 452 | matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$') 453 | } 454 | } 455 | 456 | return function (obj, opts) { 457 | let path = '' 458 | const data = obj || {} 459 | const options = opts || {} 460 | const encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent 461 | 462 | for (let i = 0; i < tokens.length; i++) { 463 | const token = tokens[i] 464 | 465 | if (typeof token === 'string') { 466 | path += token 467 | 468 | continue 469 | } 470 | 471 | const value = data[token.name || 'pathMatch'] 472 | let segment 473 | 474 | if (value == null) { 475 | if (token.optional) { 476 | // Prepend partial segment prefixes. 477 | if (token.partial) { 478 | path += token.prefix 479 | } 480 | 481 | continue 482 | } else { 483 | throw new TypeError('Expected "' + token.name + '" to be defined') 484 | } 485 | } 486 | 487 | if (Array.isArray(value)) { 488 | if (!token.repeat) { 489 | throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') 490 | } 491 | 492 | if (value.length === 0) { 493 | if (token.optional) { 494 | continue 495 | } else { 496 | throw new TypeError('Expected "' + token.name + '" to not be empty') 497 | } 498 | } 499 | 500 | for (let j = 0; j < value.length; j++) { 501 | segment = encode(value[j]) 502 | 503 | if (!matches[i].test(segment)) { 504 | throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') 505 | } 506 | 507 | path += (j === 0 ? token.prefix : token.delimiter) + segment 508 | } 509 | 510 | continue 511 | } 512 | 513 | segment = token.asterisk ? encodeAsterisk(value) : encode(value) 514 | 515 | if (!matches[i].test(segment)) { 516 | throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') 517 | } 518 | 519 | path += token.prefix + segment 520 | } 521 | 522 | return path 523 | } 524 | } 525 | 526 | /** 527 | * Format given url, append query to url query string 528 | * 529 | * @param {string} url 530 | * @param {string} query 531 | * @return {string} 532 | */ 533 | function formatUrl (url, query) { 534 | let protocol 535 | const index = url.indexOf('://') 536 | if (index !== -1) { 537 | protocol = url.substring(0, index) 538 | url = url.substring(index + 3) 539 | } else if (url.startsWith('//')) { 540 | url = url.substring(2) 541 | } 542 | 543 | let parts = url.split('/') 544 | let result = (protocol ? protocol + '://' : '//') + parts.shift() 545 | 546 | let path = parts.filter(Boolean).join('/') 547 | let hash 548 | parts = path.split('#') 549 | if (parts.length === 2) { 550 | [path, hash] = parts 551 | } 552 | 553 | result += path ? '/' + path : '' 554 | 555 | if (query && JSON.stringify(query) !== '{}') { 556 | result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query) 557 | } 558 | result += hash ? '#' + hash : '' 559 | 560 | return result 561 | } 562 | 563 | /** 564 | * Transform data object to query string 565 | * 566 | * @param {object} query 567 | * @return {string} 568 | */ 569 | function formatQuery (query) { 570 | return Object.keys(query).sort().map((key) => { 571 | const val = query[key] 572 | if (val == null) { 573 | return '' 574 | } 575 | if (Array.isArray(val)) { 576 | return val.slice().map(val2 => [key, '=', val2].join('')).join('&') 577 | } 578 | return key + '=' + val 579 | }).filter(Boolean).join('&') 580 | } 581 | -------------------------------------------------------------------------------- /.nuxt/client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import fetch from 'unfetch' 3 | import middleware from './middleware.js' 4 | import { 5 | applyAsyncData, 6 | promisify, 7 | middlewareSeries, 8 | sanitizeComponent, 9 | resolveRouteComponents, 10 | getMatchedComponents, 11 | getMatchedComponentsInstances, 12 | flatMapComponents, 13 | setContext, 14 | getLocation, 15 | compile, 16 | getQueryDiff, 17 | globalHandleError 18 | } from './utils.js' 19 | import { createApp, NuxtError } from './index.js' 20 | import NuxtLink from './components/nuxt-link.client.js' // should be included after ./index.js 21 | 22 | // Component: 23 | Vue.component(NuxtLink.name, NuxtLink) 24 | Vue.component('NLink', NuxtLink) 25 | 26 | if (!global.fetch) { global.fetch = fetch } 27 | 28 | // Global shared references 29 | let _lastPaths = [] 30 | let app 31 | let router 32 | 33 | // Try to rehydrate SSR data from window 34 | const NUXT = window.__NUXT__ || {} 35 | 36 | Object.assign(Vue.config, {"silent":false,"performance":true}) 37 | 38 | const logs = NUXT.logs || [] 39 | if (logs.length > 0) { 40 | const ssrLogSyle = 'background: #2E495E;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;' 41 | console.group && console.group ("%cNuxt SSR", ssrLogSyle) 42 | logs.forEach(logObj => (console[logObj.type] || console.log)(...logObj.args)) 43 | delete NUXT.logs 44 | console.groupEnd && console.groupEnd() 45 | } 46 | 47 | // Setup global Vue error handler 48 | if (!Vue.config.$nuxt) { 49 | const defaultErrorHandler = Vue.config.errorHandler 50 | Vue.config.errorHandler = (err, vm, info, ...rest) => { 51 | // Call other handler if exist 52 | let handled = null 53 | if (typeof defaultErrorHandler === 'function') { 54 | handled = defaultErrorHandler(err, vm, info, ...rest) 55 | } 56 | if (handled === true) { 57 | return handled 58 | } 59 | 60 | if (vm && vm.$root) { 61 | const nuxtApp = Object.keys(Vue.config.$nuxt) 62 | .find(nuxtInstance => vm.$root[nuxtInstance]) 63 | 64 | // Show Nuxt Error Page 65 | if (nuxtApp && vm.$root[nuxtApp].error && info !== 'render function') { 66 | vm.$root[nuxtApp].error(err) 67 | } 68 | } 69 | 70 | if (typeof defaultErrorHandler === 'function') { 71 | return handled 72 | } 73 | 74 | // Log to console 75 | if (process.env.NODE_ENV !== 'production') { 76 | console.error(err) 77 | } else { 78 | console.error(err.message || err) 79 | } 80 | } 81 | Vue.config.$nuxt = {} 82 | } 83 | Vue.config.$nuxt.$nuxt = true 84 | 85 | const errorHandler = Vue.config.errorHandler || console.error 86 | 87 | // Create and mount App 88 | createApp().then(mountApp).catch(errorHandler) 89 | 90 | function componentOption (component, key, ...args) { 91 | if (!component || !component.options || !component.options[key]) { 92 | return {} 93 | } 94 | const option = component.options[key] 95 | if (typeof option === 'function') { 96 | return option(...args) 97 | } 98 | return option 99 | } 100 | 101 | function mapTransitions (Components, to, from) { 102 | const componentTransitions = (component) => { 103 | const transition = componentOption(component, 'transition', to, from) || {} 104 | return (typeof transition === 'string' ? { name: transition } : transition) 105 | } 106 | 107 | return Components.map((Component) => { 108 | // Clone original object to prevent overrides 109 | const transitions = Object.assign({}, componentTransitions(Component)) 110 | 111 | // Combine transitions & prefer `leave` transitions of 'from' route 112 | if (from && from.matched.length && from.matched[0].components.default) { 113 | const fromTransitions = componentTransitions(from.matched[0].components.default) 114 | Object.keys(fromTransitions) 115 | .filter(key => fromTransitions[key] && key.toLowerCase().includes('leave')) 116 | .forEach((key) => { transitions[key] = fromTransitions[key] }) 117 | } 118 | 119 | return transitions 120 | }) 121 | } 122 | 123 | async function loadAsyncComponents (to, from, next) { 124 | // Check if route path changed (this._pathChanged), only if the page is not an error (for validate()) 125 | this._pathChanged = Boolean(app.nuxt.err) || from.path !== to.path 126 | this._queryChanged = JSON.stringify(to.query) !== JSON.stringify(from.query) 127 | this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : []) 128 | 129 | if (this._pathChanged && this.$loading.start && !this.$loading.manual) { 130 | this.$loading.start() 131 | } 132 | 133 | try { 134 | if (!this._pathChanged && this._queryChanged) { 135 | const Components = await resolveRouteComponents( 136 | to, 137 | (Component, instance) => ({ Component, instance }) 138 | ) 139 | // Add a marker on each component that it needs to refresh or not 140 | const startLoader = Components.some(({ Component, instance }) => { 141 | const watchQuery = Component.options.watchQuery 142 | if (watchQuery === true) { 143 | return true 144 | } 145 | if (Array.isArray(watchQuery)) { 146 | return watchQuery.some(key => this._diffQuery[key]) 147 | } 148 | if (typeof watchQuery === 'function') { 149 | return watchQuery.apply(instance, [to.query, from.query]) 150 | } 151 | return false 152 | }) 153 | if (startLoader && this.$loading.start && !this.$loading.manual) { 154 | this.$loading.start() 155 | } 156 | } 157 | 158 | // Call next() 159 | next() 160 | } catch (error) { 161 | const err = error || {} 162 | const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500 163 | const message = err.message || '' 164 | 165 | // Handle chunk loading errors 166 | // This may be due to a new deployment or a network problem 167 | if (/^Loading( CSS)? chunk (\d)+ failed\./.test(message)) { 168 | window.location.reload(true /* skip cache */) 169 | return // prevent error page blinking for user 170 | } 171 | 172 | this.error({ statusCode, message }) 173 | this.$nuxt.$emit('routeChanged', to, from, err) 174 | next() 175 | } 176 | } 177 | 178 | function applySSRData (Component, ssrData) { 179 | if (NUXT.serverRendered && ssrData) { 180 | applyAsyncData(Component, ssrData) 181 | } 182 | 183 | Component._Ctor = Component 184 | return Component 185 | } 186 | 187 | // Get matched components 188 | function resolveComponents (router) { 189 | const path = getLocation(router.options.base, router.options.mode) 190 | 191 | return flatMapComponents(router.match(path), async (Component, _, match, key, index) => { 192 | // If component is not resolved yet, resolve it 193 | if (typeof Component === 'function' && !Component.options) { 194 | Component = await Component() 195 | } 196 | // Sanitize it and save it 197 | const _Component = applySSRData(sanitizeComponent(Component), NUXT.data ? NUXT.data[index] : null) 198 | match.components[key] = _Component 199 | return _Component 200 | }) 201 | } 202 | 203 | function callMiddleware (Components, context, layout) { 204 | let midd = [] 205 | let unknownMiddleware = false 206 | 207 | // If layout is undefined, only call global middleware 208 | if (typeof layout !== 'undefined') { 209 | midd = [] // Exclude global middleware if layout defined (already called before) 210 | layout = sanitizeComponent(layout) 211 | if (layout.options.middleware) { 212 | midd = midd.concat(layout.options.middleware) 213 | } 214 | Components.forEach((Component) => { 215 | if (Component.options.middleware) { 216 | midd = midd.concat(Component.options.middleware) 217 | } 218 | }) 219 | } 220 | 221 | midd = midd.map((name) => { 222 | if (typeof name === 'function') { 223 | return name 224 | } 225 | if (typeof middleware[name] !== 'function') { 226 | unknownMiddleware = true 227 | this.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 228 | } 229 | return middleware[name] 230 | }) 231 | 232 | if (unknownMiddleware) { 233 | return 234 | } 235 | return middlewareSeries(midd, context) 236 | } 237 | 238 | async function render (to, from, next) { 239 | if (this._pathChanged === false && this._queryChanged === false) { 240 | return next() 241 | } 242 | // Handle first render on SPA mode 243 | if (to === from) { 244 | _lastPaths = [] 245 | } else { 246 | const fromMatches = [] 247 | _lastPaths = getMatchedComponents(from, fromMatches).map((Component, i) => { 248 | return compile(from.matched[fromMatches[i]].path)(from.params) 249 | }) 250 | } 251 | 252 | // nextCalled is true when redirected 253 | let nextCalled = false 254 | const _next = (path) => { 255 | if (from.path === path.path && this.$loading.finish) { 256 | this.$loading.finish() 257 | } 258 | 259 | if (from.path !== path.path && this.$loading.pause) { 260 | this.$loading.pause() 261 | } 262 | 263 | if (nextCalled) { 264 | return 265 | } 266 | 267 | nextCalled = true 268 | next(path) 269 | } 270 | 271 | // Update context 272 | await setContext(app, { 273 | route: to, 274 | from, 275 | next: _next.bind(this) 276 | }) 277 | this._dateLastError = app.nuxt.dateErr 278 | this._hadError = Boolean(app.nuxt.err) 279 | 280 | // Get route's matched components 281 | const matches = [] 282 | const Components = getMatchedComponents(to, matches) 283 | 284 | // If no Components matched, generate 404 285 | if (!Components.length) { 286 | // Default layout 287 | await callMiddleware.call(this, Components, app.context) 288 | if (nextCalled) { 289 | return 290 | } 291 | 292 | // Load layout for error page 293 | const layout = await this.loadLayout( 294 | typeof NuxtError.layout === 'function' 295 | ? NuxtError.layout(app.context) 296 | : NuxtError.layout 297 | ) 298 | 299 | await callMiddleware.call(this, Components, app.context, layout) 300 | if (nextCalled) { 301 | return 302 | } 303 | 304 | // Show error page 305 | app.context.error({ statusCode: 404, message: `This page could not be found` }) 306 | return next() 307 | } 308 | 309 | // Update ._data and other properties if hot reloaded 310 | Components.forEach((Component) => { 311 | if (Component._Ctor && Component._Ctor.options) { 312 | Component.options.asyncData = Component._Ctor.options.asyncData 313 | Component.options.fetch = Component._Ctor.options.fetch 314 | } 315 | }) 316 | 317 | // Apply transitions 318 | this.setTransitions(mapTransitions(Components, to, from)) 319 | 320 | try { 321 | // Call middleware 322 | await callMiddleware.call(this, Components, app.context) 323 | if (nextCalled) { 324 | return 325 | } 326 | if (app.context._errored) { 327 | return next() 328 | } 329 | 330 | // Set layout 331 | let layout = Components[0].options.layout 332 | if (typeof layout === 'function') { 333 | layout = layout(app.context) 334 | } 335 | layout = await this.loadLayout(layout) 336 | 337 | // Call middleware for layout 338 | await callMiddleware.call(this, Components, app.context, layout) 339 | if (nextCalled) { 340 | return 341 | } 342 | if (app.context._errored) { 343 | return next() 344 | } 345 | 346 | // Call .validate() 347 | let isValid = true 348 | try { 349 | for (const Component of Components) { 350 | if (typeof Component.options.validate !== 'function') { 351 | continue 352 | } 353 | 354 | isValid = await Component.options.validate(app.context) 355 | 356 | if (!isValid) { 357 | break 358 | } 359 | } 360 | } catch (validationError) { 361 | // ...If .validate() threw an error 362 | this.error({ 363 | statusCode: validationError.statusCode || '500', 364 | message: validationError.message 365 | }) 366 | return next() 367 | } 368 | 369 | // ...If .validate() returned false 370 | if (!isValid) { 371 | this.error({ statusCode: 404, message: `This page could not be found` }) 372 | return next() 373 | } 374 | 375 | let instances 376 | // Call asyncData & fetch hooks on components matched by the route. 377 | await Promise.all(Components.map((Component, i) => { 378 | // Check if only children route changed 379 | Component._path = compile(to.matched[matches[i]].path)(to.params) 380 | Component._dataRefresh = false 381 | // Check if Component need to be refreshed (call asyncData & fetch) 382 | // Only if its slug has changed or is watch query changes 383 | if ((this._pathChanged && this._queryChanged) || Component._path !== _lastPaths[i]) { 384 | Component._dataRefresh = true 385 | } else if (!this._pathChanged && this._queryChanged) { 386 | const watchQuery = Component.options.watchQuery 387 | if (watchQuery === true) { 388 | Component._dataRefresh = true 389 | } else if (Array.isArray(watchQuery)) { 390 | Component._dataRefresh = watchQuery.some(key => this._diffQuery[key]) 391 | } else if (typeof watchQuery === 'function') { 392 | if (!instances) { 393 | instances = getMatchedComponentsInstances(to) 394 | } 395 | Component._dataRefresh = watchQuery.apply(instances[i], [to.query, from.query]) 396 | } 397 | } 398 | if (!this._hadError && this._isMounted && !Component._dataRefresh) { 399 | return 400 | } 401 | 402 | const promises = [] 403 | 404 | const hasAsyncData = ( 405 | Component.options.asyncData && 406 | typeof Component.options.asyncData === 'function' 407 | ) 408 | 409 | const hasFetch = Boolean(Component.options.fetch) 410 | 411 | const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45 412 | 413 | // Call asyncData(context) 414 | if (hasAsyncData) { 415 | const promise = promisify(Component.options.asyncData, app.context) 416 | .then((asyncDataResult) => { 417 | applyAsyncData(Component, asyncDataResult) 418 | 419 | if (this.$loading.increase) { 420 | this.$loading.increase(loadingIncrease) 421 | } 422 | }) 423 | promises.push(promise) 424 | } 425 | 426 | // Check disabled page loading 427 | this.$loading.manual = Component.options.loading === false 428 | 429 | // Call fetch(context) 430 | if (hasFetch) { 431 | let p = Component.options.fetch(app.context) 432 | if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { 433 | p = Promise.resolve(p) 434 | } 435 | p.then((fetchResult) => { 436 | if (this.$loading.increase) { 437 | this.$loading.increase(loadingIncrease) 438 | } 439 | }) 440 | promises.push(p) 441 | } 442 | 443 | return Promise.all(promises) 444 | })) 445 | 446 | // If not redirected 447 | if (!nextCalled) { 448 | if (this.$loading.finish && !this.$loading.manual) { 449 | this.$loading.finish() 450 | } 451 | 452 | next() 453 | } 454 | } catch (err) { 455 | const error = err || {} 456 | if (error.message === 'ERR_REDIRECT') { 457 | return this.$nuxt.$emit('routeChanged', to, from, error) 458 | } 459 | _lastPaths = [] 460 | 461 | globalHandleError(error) 462 | 463 | // Load error layout 464 | let layout = NuxtError.layout 465 | if (typeof layout === 'function') { 466 | layout = layout(app.context) 467 | } 468 | await this.loadLayout(layout) 469 | 470 | this.error(error) 471 | this.$nuxt.$emit('routeChanged', to, from, error) 472 | next() 473 | } 474 | } 475 | 476 | // Fix components format in matched, it's due to code-splitting of vue-router 477 | function normalizeComponents (to, ___) { 478 | flatMapComponents(to, (Component, _, match, key) => { 479 | if (typeof Component === 'object' && !Component.options) { 480 | // Updated via vue-router resolveAsyncComponents() 481 | Component = Vue.extend(Component) 482 | Component._Ctor = Component 483 | match.components[key] = Component 484 | } 485 | return Component 486 | }) 487 | } 488 | 489 | function showNextPage (to) { 490 | // Hide error component if no error 491 | if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) { 492 | this.error() 493 | } 494 | 495 | // Set layout 496 | let layout = this.$options.nuxt.err 497 | ? NuxtError.layout 498 | : to.matched[0].components.default.options.layout 499 | 500 | if (typeof layout === 'function') { 501 | layout = layout(app.context) 502 | } 503 | this.setLayout(layout) 504 | } 505 | 506 | // When navigating on a different route but the same component is used, Vue.js 507 | // Will not update the instance data, so we have to update $data ourselves 508 | function fixPrepatch (to, ___) { 509 | if (this._pathChanged === false && this._queryChanged === false) { 510 | return 511 | } 512 | 513 | const instances = getMatchedComponentsInstances(to) 514 | const Components = getMatchedComponents(to) 515 | 516 | Vue.nextTick(() => { 517 | instances.forEach((instance, i) => { 518 | if (!instance || instance._isDestroyed) { 519 | return 520 | } 521 | 522 | if ( 523 | instance.constructor._dataRefresh && 524 | Components[i] === instance.constructor && 525 | instance.$vnode.data.keepAlive !== true && 526 | typeof instance.constructor.options.data === 'function' 527 | ) { 528 | const newData = instance.constructor.options.data.call(instance) 529 | for (const key in newData) { 530 | Vue.set(instance.$data, key, newData[key]) 531 | } 532 | 533 | // Ensure to trigger scroll event after calling scrollBehavior 534 | window.$nuxt.$nextTick(() => { 535 | window.$nuxt.$emit('triggerScroll') 536 | }) 537 | } 538 | }) 539 | showNextPage.call(this, to) 540 | 541 | // Hot reloading 542 | setTimeout(() => hotReloadAPI(this), 100) 543 | }) 544 | } 545 | 546 | function nuxtReady (_app) { 547 | window.onNuxtReadyCbs.forEach((cb) => { 548 | if (typeof cb === 'function') { 549 | cb(_app) 550 | } 551 | }) 552 | // Special JSDOM 553 | if (typeof window._onNuxtLoaded === 'function') { 554 | window._onNuxtLoaded(_app) 555 | } 556 | // Add router hooks 557 | router.afterEach((to, from) => { 558 | // Wait for fixPrepatch + $data updates 559 | Vue.nextTick(() => _app.$nuxt.$emit('routeChanged', to, from)) 560 | }) 561 | } 562 | 563 | const noopData = () => { return {} } 564 | const noopFetch = () => {} 565 | 566 | // Special hot reload with asyncData(context) 567 | function getNuxtChildComponents ($parent, $components = []) { 568 | $parent.$children.forEach(($child) => { 569 | if ($child.$vnode && $child.$vnode.data.nuxtChild && !$components.find(c =>(c.$options.__file === $child.$options.__file))) { 570 | $components.push($child) 571 | } 572 | if ($child.$children && $child.$children.length) { 573 | getNuxtChildComponents($child, $components) 574 | } 575 | }) 576 | 577 | return $components 578 | } 579 | 580 | function hotReloadAPI(_app) { 581 | if (!module.hot) return 582 | 583 | let $components = getNuxtChildComponents(_app.$nuxt, []) 584 | 585 | $components.forEach(addHotReload.bind(_app)) 586 | } 587 | 588 | function addHotReload ($component, depth) { 589 | if ($component.$vnode.data._hasHotReload) return 590 | $component.$vnode.data._hasHotReload = true 591 | 592 | var _forceUpdate = $component.$forceUpdate.bind($component.$parent) 593 | 594 | $component.$vnode.context.$forceUpdate = async () => { 595 | let Components = getMatchedComponents(router.currentRoute) 596 | let Component = Components[depth] 597 | if (!Component) { 598 | return _forceUpdate() 599 | } 600 | if (typeof Component === 'object' && !Component.options) { 601 | // Updated via vue-router resolveAsyncComponents() 602 | Component = Vue.extend(Component) 603 | Component._Ctor = Component 604 | } 605 | this.error() 606 | let promises = [] 607 | const next = function (path) { 608 | this.$loading.finish && this.$loading.finish() 609 | router.push(path) 610 | } 611 | await setContext(app, { 612 | route: router.currentRoute, 613 | isHMR: true, 614 | next: next.bind(this) 615 | }) 616 | const context = app.context 617 | 618 | if (this.$loading.start && !this.$loading.manual) { 619 | this.$loading.start() 620 | } 621 | 622 | callMiddleware.call(this, Components, context) 623 | .then(() => { 624 | // If layout changed 625 | if (depth !== 0) { 626 | return 627 | } 628 | 629 | let layout = Component.options.layout || 'default' 630 | if (typeof layout === 'function') { 631 | layout = layout(context) 632 | } 633 | if (this.layoutName === layout) { 634 | return 635 | } 636 | let promise = this.loadLayout(layout) 637 | promise.then(() => { 638 | this.setLayout(layout) 639 | Vue.nextTick(() => hotReloadAPI(this)) 640 | }) 641 | return promise 642 | }) 643 | 644 | .then(() => { 645 | return callMiddleware.call(this, Components, context, this.layout) 646 | }) 647 | 648 | .then(() => { 649 | // Call asyncData(context) 650 | let pAsyncData = promisify(Component.options.asyncData || noopData, context) 651 | pAsyncData.then((asyncDataResult) => { 652 | applyAsyncData(Component, asyncDataResult) 653 | this.$loading.increase && this.$loading.increase(30) 654 | }) 655 | promises.push(pAsyncData) 656 | 657 | // Call fetch() 658 | Component.options.fetch = Component.options.fetch || noopFetch 659 | let pFetch = Component.options.fetch(context) 660 | if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) } 661 | pFetch.then(() => this.$loading.increase && this.$loading.increase(30)) 662 | promises.push(pFetch) 663 | 664 | return Promise.all(promises) 665 | }) 666 | .then(() => { 667 | this.$loading.finish && this.$loading.finish() 668 | _forceUpdate() 669 | setTimeout(() => hotReloadAPI(this), 100) 670 | }) 671 | } 672 | } 673 | 674 | async function mountApp (__app) { 675 | // Set global variables 676 | app = __app.app 677 | router = __app.router 678 | 679 | // Create Vue instance 680 | const _app = new Vue(app) 681 | 682 | // Mounts Vue app to DOM element 683 | const mount = () => { 684 | _app.$mount('#__nuxt') 685 | 686 | // Add afterEach router hooks 687 | router.afterEach(normalizeComponents) 688 | router.afterEach(fixPrepatch.bind(_app)) 689 | 690 | // Listen for first Vue update 691 | Vue.nextTick(() => { 692 | // Call window.{{globals.readyCallback}} callbacks 693 | nuxtReady(_app) 694 | 695 | // Enable hot reloading 696 | hotReloadAPI(_app) 697 | }) 698 | } 699 | 700 | // Resolve route components 701 | const Components = await Promise.all(resolveComponents(router)) 702 | 703 | // Enable transitions 704 | _app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app) 705 | if (Components.length) { 706 | _app.setTransitions(mapTransitions(Components, router.currentRoute)) 707 | _lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params)) 708 | } 709 | 710 | // Initialize error handler 711 | _app.$loading = {} // To avoid error while _app.$nuxt does not exist 712 | if (NUXT.error) { 713 | _app.error(NUXT.error) 714 | } 715 | 716 | // Add beforeEach router hooks 717 | router.beforeEach(loadAsyncComponents.bind(_app)) 718 | router.beforeEach(render.bind(_app)) 719 | 720 | // If page already is server rendered 721 | if (NUXT.serverRendered) { 722 | mount() 723 | return 724 | } 725 | 726 | // First render on client-side 727 | const clientFirstMount = () => { 728 | normalizeComponents(router.currentRoute, router.currentRoute) 729 | showNextPage.call(_app, router.currentRoute) 730 | // Don't call fixPrepatch.call(_app, router.currentRoute, router.currentRoute) since it's first render 731 | mount() 732 | } 733 | 734 | render.call(_app, router.currentRoute, router.currentRoute, (path) => { 735 | // If not redirected 736 | if (!path) { 737 | clientFirstMount() 738 | return 739 | } 740 | 741 | // Add a one-time afterEach hook to 742 | // mount the app wait for redirect and route gets resolved 743 | const unregisterHook = router.afterEach((to, from) => { 744 | unregisterHook() 745 | clientFirstMount() 746 | }) 747 | 748 | // Push the path and let route to be resolved 749 | router.push(path, undefined, (err) => { 750 | if (err) { 751 | errorHandler(err) 752 | } 753 | }) 754 | }) 755 | } 756 | --------------------------------------------------------------------------------