diff --git a/README.md b/README.md
index 3bccddeef5..03167abc4f 100644
--- a/README.md
+++ b/README.md
@@ -99,7 +99,7 @@ Learn more: https://nuxtjs.org/api/nuxt
## Using nuxt.js as a middleware
-You might want to use your own server with you configurations, your API and everything awesome your created with. That's why you can use nuxt.js as a middleware. It's recommended to use it at the end of your middlewares since it will handle the rendering of your web application and won't call next().
+You might want to use your own server with you configurations, your API and everything awesome your created with. That's why you can use nuxt.js as a middleware. It's recommended to use it at the end of your middleware since it will handle the rendering of your web application and won't call next().
```js
app.use(nuxt.render)
diff --git a/bin/nuxt-generate b/bin/nuxt-generate
index 8feabf3c0c..10b1c4ff70 100755
--- a/bin/nuxt-generate
+++ b/bin/nuxt-generate
@@ -17,7 +17,7 @@ if (fs.existsSync(nuxtConfigFile)) {
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir
}
-options.dev = false // Force production mode (no webpack middlewares called)
+options.dev = false // Force production mode (no webpack middleware called)
console.log('[nuxt] Generating...') // eslint-disable-line no-console
var nuxt = new Nuxt(options)
diff --git a/bin/nuxt-start b/bin/nuxt-start
index 0f9aef3577..9190178576 100755
--- a/bin/nuxt-start
+++ b/bin/nuxt-start
@@ -14,7 +14,7 @@ if (fs.existsSync(nuxtConfigFile)) {
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir
}
-options.dev = false // Force production mode (no webpack middlewares called)
+options.dev = false // Force production mode (no webpack middleware called)
var nuxt = new Nuxt(options)
diff --git a/examples/auth-routes/middleware/auth.js b/examples/auth-routes/middleware/auth.js
new file mode 100644
index 0000000000..2aeeafef6b
--- /dev/null
+++ b/examples/auth-routes/middleware/auth.js
@@ -0,0 +1,10 @@
+export default function ({ store, redirect, error }) {
+ // If user not connected, redirect to /
+ if (!store.state.authUser) {
+ // return redirect('/')
+ error({
+ message: 'You are not connected',
+ statusCode: 403
+ })
+ }
+}
diff --git a/examples/auth-routes/nuxt.config.js b/examples/auth-routes/nuxt.config.js
index 0434d79bcc..6927fa17d8 100644
--- a/examples/auth-routes/nuxt.config.js
+++ b/examples/auth-routes/nuxt.config.js
@@ -6,6 +6,5 @@ module.exports = {
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', content: 'Auth Routes example' }
]
- },
- loading: { color: '#3B8070' }
+ }
}
diff --git a/examples/auth-routes/package.json b/examples/auth-routes/package.json
index 87677b2922..3cd143afbf 100644
--- a/examples/auth-routes/package.json
+++ b/examples/auth-routes/package.json
@@ -2,12 +2,12 @@
"name": "auth-routes",
"description": "",
"dependencies": {
+ "axios": "^0.15.3",
"body-parser": "^1.15.2",
"cross-env": "^3.1.3",
"express": "^4.14.0",
"express-session": "^1.14.2",
- "nuxt": "latest",
- "whatwg-fetch": "^2.0.1"
+ "nuxt": "latest"
},
"scripts": {
"dev": "node server.js",
diff --git a/examples/auth-routes/pages/secret.vue b/examples/auth-routes/pages/secret.vue
index 776201a1e7..c04a37363c 100644
--- a/examples/auth-routes/pages/secret.vue
+++ b/examples/auth-routes/pages/secret.vue
@@ -8,11 +8,6 @@
diff --git a/examples/auth-routes/server.js b/examples/auth-routes/server.js
index 5a194c6210..5a2fb970d1 100644
--- a/examples/auth-routes/server.js
+++ b/examples/auth-routes/server.js
@@ -1,7 +1,9 @@
-const Nuxt = require('nuxt')
+const Nuxt = require('../../')
const bodyParser = require('body-parser')
const session = require('express-session')
const app = require('express')()
+const host = process.env.HOST || '127.0.0.1'
+const port = process.env.PORT || '3000'
// Body parser, to access req.body
app.use(bodyParser.json())
@@ -20,7 +22,7 @@ app.post('/api/login', function (req, res) {
req.session.authUser = { username: 'demo' }
return res.json({ username: 'demo' })
}
- res.status(401).json({ error: 'Bad credentials' })
+ res.status(401).json({ message: 'Bad credentials' })
})
// POST /api/logout to log out the user and remove it from the req.session
@@ -29,19 +31,23 @@ app.post('/api/logout', function (req, res) {
res.json({ ok: true })
})
-// We instantiate Nuxt.js with the options
-const isProd = process.env.NODE_ENV === 'production'
+// Import and Set Nuxt.js options
let config = require('./nuxt.config.js')
-config.dev = !isProd
+config.dev = !(process.env.NODE_ENV === 'production')
+
+// Init Nuxt.js
const nuxt = new Nuxt(config)
-// No build in production
-const promise = (isProd ? Promise.resolve() : nuxt.build())
-promise.then(() => {
- app.use(nuxt.render)
- app.listen(3000)
- console.log('Server is listening on http://localhost:3000') // eslint-disable-line no-console
-})
-.catch((error) => {
- console.error(error) // eslint-disable-line no-console
- process.exit(1)
-})
+app.use(nuxt.render)
+
+// Build only in dev mode
+if (config.dev) {
+ nuxt.build()
+ .catch((error) => {
+ console.error(error) // eslint-disable-line no-console
+ process.exit(1)
+ })
+}
+
+// Listen the server
+app.listen(port, host)
+console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console
diff --git a/examples/auth-routes/store/index.js b/examples/auth-routes/store/index.js
index 6f2a38f1a0..2181cfdf0c 100644
--- a/examples/auth-routes/store/index.js
+++ b/examples/auth-routes/store/index.js
@@ -1,69 +1,41 @@
-import Vue from 'vue'
-import Vuex from 'vuex'
+import axios from 'axios'
-Vue.use(Vuex)
+export const state = {
+ authUser: null
+}
-// Polyfill for window.fetch()
-require('whatwg-fetch')
+export const mutations = {
+ SET_USER: function (state, user) {
+ state.authUser = user
+ }
+}
-const store = new Vuex.Store({
-
- state: {
- authUser: null
- },
-
- mutations: {
- SET_USER: function (state, user) {
- state.authUser = user
+export const actions = {
+ nuxtServerInit ({ commit }, { req }) {
+ if (req.session && req.session.authUser) {
+ commit('SET_USER', req.session.authUser)
}
},
-
- actions: {
-
- nuxtServerInit ({ commit }, { req }) {
- if (req.session && req.session.authUser) {
- commit('SET_USER', req.session.authUser)
+ login ({ commit }, { username, password }) {
+ return axios.post('/api/login', {
+ username,
+ password
+ })
+ .then((res) => {
+ commit('SET_USER', res.data)
+ })
+ .catch((error) => {
+ if (error.response.status === 401) {
+ throw new Error('Bad credentials')
}
- },
-
- login ({ commit }, { username, password }) {
- return fetch('/api/login', {
- // Send the client cookies to the server
- credentials: 'same-origin',
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- username,
- password
- })
- })
- .then((res) => {
- if (res.status === 401) {
- throw new Error('Bad credentials')
- } else {
- return res.json()
- }
- })
- .then((authUser) => {
- commit('SET_USER', authUser)
- })
- },
-
- logout ({ commit }) {
- return fetch('/api/logout', {
- // Send the client cookies to the server
- credentials: 'same-origin',
- method: 'POST'
- })
- .then(() => {
- commit('SET_USER', null)
- })
- }
+ })
+ },
+ logout ({ commit }) {
+ return axios.post('/api/logout')
+ .then(() => {
+ commit('SET_USER', null)
+ })
}
-})
-
-export default store
+}
diff --git a/examples/middleware/components/Visits.vue b/examples/middleware/components/Visits.vue
new file mode 100644
index 0000000000..0ac86d54d4
--- /dev/null
+++ b/examples/middleware/components/Visits.vue
@@ -0,0 +1,40 @@
+
+
+ - {{ visit.date | hours }} - {{ visit.path }}
+
+
+
+
+
+
diff --git a/examples/middleware/layouts/default.vue b/examples/middleware/layouts/default.vue
new file mode 100644
index 0000000000..07f12ba8a8
--- /dev/null
+++ b/examples/middleware/layouts/default.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/examples/middleware/middleware/user-agent.js b/examples/middleware/middleware/user-agent.js
new file mode 100644
index 0000000000..097436a73d
--- /dev/null
+++ b/examples/middleware/middleware/user-agent.js
@@ -0,0 +1,3 @@
+export default function (context) {
+ context.userAgent = context.isServer ? context.req.headers['user-agent'] : navigator.userAgent
+}
diff --git a/examples/middleware/middleware/visits.js b/examples/middleware/middleware/visits.js
new file mode 100644
index 0000000000..3ba5ab253f
--- /dev/null
+++ b/examples/middleware/middleware/visits.js
@@ -0,0 +1,3 @@
+export default function ({ store, route }) {
+ store.commit('ADD_VISIT', route.path)
+}
diff --git a/examples/middleware/nuxt.config.js b/examples/middleware/nuxt.config.js
new file mode 100644
index 0000000000..1e075eff57
--- /dev/null
+++ b/examples/middleware/nuxt.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ router: {
+ middleware: ['visits', 'user-agent']
+ }
+}
diff --git a/examples/middleware/package.json b/examples/middleware/package.json
new file mode 100644
index 0000000000..7d068bad95
--- /dev/null
+++ b/examples/middleware/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "nuxt-middleware",
+ "dependencies": {
+ "nuxt": "latest"
+ },
+ "scripts": {
+ "dev": "nuxt",
+ "build": "nuxt build",
+ "start": "nuxt start"
+ }
+}
diff --git a/examples/middleware/pages/_slug.vue b/examples/middleware/pages/_slug.vue
new file mode 100644
index 0000000000..9694907931
--- /dev/null
+++ b/examples/middleware/pages/_slug.vue
@@ -0,0 +1,25 @@
+
+
+
{{ $route.params.slug || 'Home' }}
+
{{ userAgent }}
+
+
+
+
+
diff --git a/examples/middleware/store/index.js b/examples/middleware/store/index.js
new file mode 100644
index 0000000000..12cdd3f02b
--- /dev/null
+++ b/examples/middleware/store/index.js
@@ -0,0 +1,12 @@
+export const state = {
+ visits: []
+}
+
+export const mutations = {
+ ADD_VISIT (state, path) {
+ state.visits.push({
+ path,
+ date: new Date().toJSON()
+ })
+ }
+}
diff --git a/lib/app/client.js b/lib/app/client.js
index aac145d260..027ee0dc3d 100644
--- a/lib/app/client.js
+++ b/lib/app/client.js
@@ -1,8 +1,9 @@
'use strict'
import Vue from 'vue'
+import middleware from './middleware'
import { app, router<%= (store ? ', store' : '') %> } from './index'
-import { getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promisify, getLocation, compile } from './utils'
+import { getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, promiseSeries, promisify, getLocation, compile } from './utils'
const noopData = () => { return {} }
const noopFetch = () => {}
let _lastPaths = []
@@ -51,16 +52,44 @@ function loadAsyncComponents (to, from, next) {
})
}
+function callMiddleware (Components, context, layout) {
+ // Call middleware
+ let midd = <%= serialize(router.middleware, { isJSON: true }) %>
+ if (layout.middleware) {
+ midd = midd.concat(layout.middleware)
+ }
+ Components.forEach((Component) => {
+ if (Component.options.middleware) {
+ midd = midd.concat(Component.options.middleware)
+ }
+ })
+ midd = midd.map((name) => {
+ if (typeof middleware[name] !== 'function') {
+ this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
+ }
+ return middleware[name]
+ })
+ if (this.$options._nuxt.err) return
+ return promiseSeries(midd, context)
+}
+
function render (to, from, next) {
if (this._hashChanged) return next()
+ const _next = function (path) {
+ <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
+ nextCalled = true
+ next(path)
+ }
+ const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) })
let Components = getMatchedComponents(to)
this._dateLastError = this.$options._nuxt.dateErr
this._hadError = !!this.$options._nuxt.err
if (!Components.length) {
// Default layout
this.setLayout()
+ .then(callMiddleware.bind(this, Components, context))
.then(() => {
- this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path })
+ this.error({ statusCode: 404, message: 'This page could not be found.' })
return next()
})
return
@@ -87,6 +116,7 @@ function render (to, from, next) {
let nextCalled = false
// Set layout
this.setLayout(Components[0].options.layout)
+ .then(callMiddleware.bind(this, Components, context))
.then(() => {
// Pass validation?
let isValid = true
@@ -99,7 +129,7 @@ function render (to, from, next) {
})
})
if (!isValid) {
- this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path })
+ this.error({ statusCode: 404, message: 'This page could not be found.' })
return next()
}
return Promise.all(Components.map((Component, i) => {
@@ -109,12 +139,6 @@ function render (to, from, next) {
return Promise.resolve()
}
let promises = []
- const _next = function (path) {
- <%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
- nextCalled = true
- next(path)
- }
- const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) })
// Validate method
if (Component._data && typeof Component._data === 'function') {
var promise = promisify(Component._data, context)
diff --git a/lib/app/middleware.js b/lib/app/middleware.js
new file mode 100644
index 0000000000..3669e0545b
--- /dev/null
+++ b/lib/app/middleware.js
@@ -0,0 +1,20 @@
+<% if (middleware) { %>
+let files = require.context('~/middleware', false, /^\.\/.*\.js$/)
+let filenames = files.keys()
+
+function getModule (filename) {
+ let file = files(filename)
+ return file.default
+ ? file.default
+ : file
+}
+let middleware = {}
+
+// Generate the middleware
+for (let filename of filenames) {
+ let name = filename.replace(/^\.\//, '').replace(/\.js$/, '')
+ middleware[name] = getModule(filename)
+}
+
+export default middleware
+<% } else { %>export default {}<% } %>
diff --git a/lib/app/server.js b/lib/app/server.js
index e8e184226f..9c04c56fdb 100644
--- a/lib/app/server.js
+++ b/lib/app/server.js
@@ -5,8 +5,9 @@ debug.color = 4 // force blue color
import Vue from 'vue'
import { stringify } from 'querystring'
import { omit } from 'lodash'
+import middleware from './middleware'
import { app, router<%= (store ? ', store' : '') %> } from './index'
-import { getMatchedComponents, getContext, promisify, urlJoin } from './utils'
+import { getMatchedComponents, getContext, promiseSeries, promisify, urlJoin } from './utils'
const isDev = <%= isDev %>
const _app = new Vue(app)
@@ -47,6 +48,7 @@ export default context => {
context.error = _app.$options._nuxt.error.bind(_app)
<%= (isDev ? 'const s = isDev && Date.now()' : '') %>
+ const ctx = getContext(context)
let Components = getMatchedComponents(context.route)
<% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context), 'redirect', 'error')) : null)
@@ -71,6 +73,26 @@ export default context => {
// Set layout
return _app.setLayout(Components.length ? Components[0].options.layout : '')
})
+ .then((layout) => {
+ // Call middleware
+ let midd = <%= serialize(router.middleware, { isJSON: true }) %>
+ if (layout.middleware) {
+ midd = midd.concat(layout.middleware)
+ }
+ Components.forEach((Component) => {
+ if (Component.options.middleware) {
+ midd = midd.concat(Component.options.middleware)
+ }
+ })
+ midd = midd.map((name) => {
+ if (typeof middleware[name] !== 'function') {
+ context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
+ }
+ return middleware[name]
+ })
+ if (context.nuxt.error) return
+ return promiseSeries(midd, ctx)
+ })
.then(() => {
// Call .validate()
let isValid = true
@@ -94,7 +116,6 @@ export default context => {
// Call data & fetch hooks on components matched by the route.
return Promise.all(Components.map((Component) => {
let promises = []
- const ctx = getContext(context)
if (Component.options.data && typeof Component.options.data === 'function') {
Component._data = Component.options.data
let promise = promisify(Component._data, ctx)
@@ -114,7 +135,7 @@ export default context => {
})
.then((res) => {
if (!Components.length) {
- context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.', url: context.route.path })
+ context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.' })
<%= (store ? 'context.nuxt.state = store.state' : '') %>
return _app
}
diff --git a/lib/app/store.js b/lib/app/store.js
index adbc0ec4d2..218eeebebc 100644
--- a/lib/app/store.js
+++ b/lib/app/store.js
@@ -2,15 +2,8 @@ import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
-let files
-let filenames = []
-
-try {
- files = require.context('~store', false, /^\.\/.*\.js$/)
- filenames = files.keys()
-} catch (e) {
- console.warn('Nuxt.js store:', e.message)
-}
+let files = require.context('~/store', false, /^\.\/.*\.js$/)
+let filenames = files.keys()
function getModule (filename) {
let file = files(filename)
diff --git a/lib/app/utils.js b/lib/app/utils.js
index a06d68f393..013b3d4b29 100644
--- a/lib/app/utils.js
+++ b/lib/app/utils.js
@@ -40,6 +40,7 @@ export function getContext (context) {
ctx.query = ctx.route.query || {}
ctx.redirect = function (status, path, query) {
if (!status) return
+ ctx._redirected = true
// if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' })
if (typeof status === 'string' && (typeof path === 'undefined' || typeof path === 'object')) {
query = path || {}
@@ -57,6 +58,16 @@ export function getContext (context) {
return ctx
}
+export function promiseSeries (promises, context) {
+ if (!promises.length || context._redirected) {
+ return Promise.resolve()
+ }
+ return promisify(promises[0], context)
+ .then(() => {
+ return promiseSeries(promises.slice(1), context)
+ })
+}
+
export function promisify (fn, context) {
let promise
if (fn.length === 2) {
diff --git a/lib/build.js b/lib/build.js
index b988fd1083..987a6aa088 100644
--- a/lib/build.js
+++ b/lib/build.js
@@ -120,8 +120,8 @@ export function * build () {
function * buildFiles () {
if (this.dev) {
- debug('Adding webpack middlewares...')
- createWebpackMiddlewares.call(this)
+ debug('Adding webpack middleware...')
+ createWebpackMiddleware.call(this)
webpackWatchAndUpdate.call(this)
watchPages.call(this)
} else {
@@ -148,12 +148,17 @@ function * generateRoutesAndFiles () {
this.routes = _.uniq(_.map(files, (file) => {
return file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/index/g, '').replace(/_/g, ':').replace('', '/').replace(/\/{2,}/g, '/')
}))
+ if (typeof this.options.router.extendRoutes === 'function') {
+ // let the user extend the routes
+ this.options.router.extendRoutes(this.routes)
+ }
// Interpret and move template files to .nuxt/
debug('Generating files...')
let templatesFiles = [
'App.vue',
'client.js',
'index.js',
+ 'middleware.js',
'router.js',
'server.js',
'utils.js',
@@ -168,11 +173,13 @@ function * generateRoutesAndFiles () {
isDev: this.dev,
router: {
base: this.options.router.base,
+ middleware: this.options.router.middleware,
linkActiveClass: this.options.router.linkActiveClass,
scrollBehavior: this.options.router.scrollBehavior
},
env: this.options.env,
head: this.options.head,
+ middleware: this.options.middleware,
store: this.options.store,
css: this.options.css,
plugins: this.options.plugins.map((p) => r(this.srcDir, p)),
@@ -294,7 +301,7 @@ function getWebpackServerConfig () {
return serverWebpackConfig.call(this)
}
-function createWebpackMiddlewares () {
+function createWebpackMiddleware () {
const clientConfig = getWebpackClientConfig.call(this)
// setup on the fly compilation + hot-reload
clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
@@ -303,7 +310,7 @@ function createWebpackMiddlewares () {
new webpack.NoEmitOnErrorsPlugin()
)
const clientCompiler = webpack(clientConfig)
- // Add the middlewares to the instance context
+ // Add the middleware to the instance context
this.webpackDevMiddleware = pify(require('webpack-dev-middleware')(clientCompiler, {
publicPath: clientConfig.output.publicPath,
stats: {
diff --git a/lib/generate.js b/lib/generate.js
index b8bdd05bd6..9f37dfc469 100644
--- a/lib/generate.js
+++ b/lib/generate.js
@@ -76,6 +76,7 @@ export default function () {
console.error(`Could not generate the dynamic route ${route}, please add the mapping params in nuxt.config.js (generate.routeParams).`) // eslint-disable-line no-console
return process.exit(1)
}
+ route = route + '?'
const toPath = pathToRegexp.compile(route)
routes = routes.concat(routeParams.map((params) => {
return toPath(params)
diff --git a/lib/nuxt.js b/lib/nuxt.js
index 1ed5ab7a51..6982b29049 100644
--- a/lib/nuxt.js
+++ b/lib/nuxt.js
@@ -37,13 +37,16 @@ class Nuxt {
},
router: {
base: '/',
+ middleware: [],
linkActiveClass: 'nuxt-link-active',
extendRoutes: null,
scrollBehavior: null
},
build: {}
}
+ // Sanitization
if (options.loading === true) delete options.loading
+ if (options.router && typeof options.router.middleware === 'string') options.router.middleware = [ options.router.middleware ]
if (typeof options.transition === 'string') options.transition = { name: options.transition }
this.options = _.defaultsDeep(options, defaults)
// Env variables
@@ -54,6 +57,11 @@ class Nuxt {
if (fs.existsSync(join(this.srcDir, 'store'))) {
this.options.store = true
}
+ // If middleware defined, update middleware option to true
+ this.options.middleware = false
+ if (fs.existsSync(join(this.srcDir, 'middleware'))) {
+ this.options.middleware = true
+ }
// Template
this.appTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'app.html'), 'utf8'), {
imports: { serialize }
diff --git a/lib/render.js b/lib/render.js
index 6a9cd02d4b..a759f1345d 100644
--- a/lib/render.js
+++ b/lib/render.js
@@ -22,11 +22,11 @@ export function render (req, res) {
const context = getContext(req, res)
return co(function * () {
if (self.dev) {
- // Call webpack middlewares only in development
+ // Call webpack middleware only in development
yield self.webpackDevMiddleware(req, res)
yield self.webpackHotMiddleware(req, res)
}
- // If base in req.url, remove it for the middlewares and vue-router
+ // If base in req.url, remove it for the middleware and vue-router
if (self.options.router.base !== '/' && req.url.indexOf(self.options.router.base) === 0) {
// Compatibility with base url for dev server
req.url = req.url.replace(self.options.router.base, '/')