├── client ├── assets │ ├── img │ │ └── logo.png │ └── css │ │ └── main.css ├── static │ └── favicon.ico ├── middleware │ └── auth.js ├── components │ └── Footer.vue ├── store │ ├── index.js │ ├── README.md │ └── about.js ├── app.html ├── layouts │ ├── error.vue │ └── default.vue └── pages │ ├── index.vue │ └── about.vue ├── backpack.config.js ├── config ├── app.alp.js ├── app.dev.js ├── app.pro.js ├── app.uat.js └── log4js.js ├── .gitignore ├── .eslintrc.js ├── server ├── controllers │ ├── error.js │ └── user.js ├── autoRoutes.js └── index.js ├── README.md ├── nuxt.config.js ├── package.json └── helper ├── services.js └── utils.js /client/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1peng/vue2_koa2_nuxt_ssr/HEAD/client/assets/img/logo.png -------------------------------------------------------------------------------- /client/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1peng/vue2_koa2_nuxt_ssr/HEAD/client/static/favicon.ico -------------------------------------------------------------------------------- /backpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: (config, options, webpack) => { 3 | config.entry.main = './server/index.js' 4 | return config 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /client/middleware/auth.js: -------------------------------------------------------------------------------- 1 | export default function (context) { 2 | context.userAgent = context.isServer ? context.req.headers['user-agent'] : navigator.userAgent 3 | } 4 | -------------------------------------------------------------------------------- /client/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /config/app.alp.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiPath: { 3 | local: { 4 | host: 'http://localhost:3000', 5 | getUserList: { 6 | method: 'get', 7 | url: '/user/list' 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/app.dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiPath: { 3 | local: { 4 | host: 'http://localhost:3000', 5 | getUserList: { 6 | method: 'get', 7 | url: '/user/list' 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/app.pro.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiPath: { 3 | local: { 4 | host: 'http://localhost:3000', 5 | getUserList: { 6 | method: 'get', 7 | url: '/user/list' 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/app.uat.js: -------------------------------------------------------------------------------- 1 | console.log('uat') 2 | module.exports = { 3 | apiPath: { 4 | local: { 5 | host: 'http://localhost:3000', 6 | getUserList: { 7 | method: 'get', 8 | url: '/user/list' 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | 7 | # Nuxt build 8 | .nuxt 9 | 10 | # Nuxt generate 11 | dist 12 | 13 | # build 14 | build 15 | 16 | # .idea 17 | .idea 18 | 19 | # server/logs 20 | server/logs 21 | 22 | # config/app.js 23 | config/app.js 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | env: { 5 | browser: true, 6 | node: true 7 | }, 8 | extends: 'standard', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'html' 12 | ], 13 | // add your custom rules here 14 | rules: {}, 15 | globals: {} 16 | } 17 | -------------------------------------------------------------------------------- /client/store/index.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | counter: 0 3 | }) 4 | 5 | export const getters = { 6 | counter (state) { 7 | return state.counter 8 | } 9 | } 10 | 11 | export const mutations = { 12 | increment (state) { 13 | state.counter++ 14 | }, 15 | reduction (state) { 16 | state.counter-- 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/controllers/error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangyipeng on 2017/9/27. 3 | */ 4 | const router = require('koa-router')() 5 | router.get('/reporter', async function (ctx, next) { 6 | let logger = ctx.Log4js.getLogger('reporter') 7 | logger.error(ctx.query.err) 8 | ctx.body = {result: 'ok'} 9 | }) 10 | 11 | module.exports = router 12 | -------------------------------------------------------------------------------- /client/store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | This directory contains your Vuex Store files. 4 | Vuex Store option is implemented in the Nuxt.js framework. 5 | Creating a index.js file in this directory activate the option in the framework automatically. 6 | 7 | More information about the usage of this directory in the documentation: 8 | https://nuxtjs.org/guide/vuex-store 9 | 10 | **This directory is not required, you can delete it if you don't want to use it.** 11 | -------------------------------------------------------------------------------- /server/controllers/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangyipeng on 2017/9/27. 3 | */ 4 | import services from '../../helper/services' 5 | const router = require('koa-router')() 6 | router.get('/index', async function (ctx, next) { 7 | let advList = await services.local.getUserList() 8 | ctx.body = advList 9 | }) 10 | 11 | router.get('/list', async function (ctx, next) { 12 | ctx.body = [ 13 | { 14 | name: 'yipeng', age: '29' 15 | }, 16 | { 17 | name: 'yihang', age: '18' 18 | } 19 | ] 20 | }) 21 | 22 | module.exports = router 23 | -------------------------------------------------------------------------------- /client/assets/css/main.css: -------------------------------------------------------------------------------- 1 | html, body 2 | { 3 | background-color: #fff; 4 | color: #2e2f30; 5 | letter-spacing: 0.5px; 6 | font-family: "Source Sans Pro", Arial, sans-serif; 7 | height: 100vh; 8 | margin: 0; 9 | } 10 | 11 | footer 12 | { 13 | padding: 20px; 14 | text-align: center; 15 | border-top: 1px solid #ddd; 16 | } 17 | 18 | a, a:hover, a:focus, a:visited 19 | { 20 | color: #41B883; 21 | } 22 | 23 | .logo { 24 | width: 243px; 25 | height: 218px; 26 | } 27 | 28 | .page-enter-active, .page-leave-active 29 | { 30 | transition: opacity .5s 31 | } 32 | .page-enter, .page-leave-active 33 | { 34 | opacity: 0 35 | } 36 | -------------------------------------------------------------------------------- /client/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ HEAD }} 5 | 18 | 19 | 20 | {{ APP }} 21 | 22 | -------------------------------------------------------------------------------- /client/store/about.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | abouts: [ 3 | { 4 | text: 'aaaa11', 5 | done: false 6 | }, 7 | { 8 | text: 'aaaa2222', 9 | done: false 10 | }, 11 | { 12 | text: '222221119999999922', 13 | done: false 14 | } 15 | ] 16 | }) 17 | export const getters = { 18 | abouts (state) { 19 | return state.abouts 20 | } 21 | } 22 | 23 | export const mutations = { 24 | add (state, text) { 25 | state.abouts.push({ 26 | text: text, 27 | done: false 28 | }) 29 | }, 30 | remove (state, { todo }) { 31 | state.abouts.splice(state.abouts.indexOf(todo), 1) 32 | }, 33 | toggle (state, todo) { 34 | todo.done = !todo.done 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue2_koa2_nuxt_ssr 2 | 3 | > A vue project with koa2 and nuxt for ssr. 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm install # Or yarn install*[see note below] 10 | 11 | # serve with hot reload at localhost:3000 12 | $ npm run dev 13 | 14 | # build for production and launch server 15 | $ npm run build 16 | $ npm start 17 | 18 | # generate static project 19 | $ npm run generate 20 | ``` 21 | 22 | *Note: Due to a bug in yarn's engine version detection code if you are 23 | using a prerelease version of Node (i.e. v7.6.0-rc.1) you will need to either: 24 | 1. Use `npm install` 25 | 2. Run `yarn` with a standard release of Node and then switch back 26 | 27 | For detailed explanation on how things work, checkout the [Nuxt.js docs](https://github.com/nuxt/nuxt.js). 28 | -------------------------------------------------------------------------------- /config/log4js.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'appenders': [ 3 | { 4 | 'type': 'console' 5 | }, 6 | { 7 | 'type': 'clustered', 8 | 'appenders': [ 9 | { 10 | 'type': 'dateFile', 11 | 'filename': 'http.log', 12 | 'pattern': '-yyyy-MM-dd', 13 | 'category': 'http' 14 | }, 15 | { 16 | 'type': 'file', 17 | 'filename': 'app.log', 18 | 'maxLogSize': 10485760, 19 | 'pattern': '-yyyy-MM-dd', 20 | 'numBackups': 5 21 | }, 22 | { 23 | 'type': 'logLevelFilter', 24 | 'level': 'ERROR', 25 | 'appender': { 26 | 'type': 'file', 27 | 'filename': 'errors.log' 28 | } 29 | } 30 | ] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /server/autoRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wangyipeng on 2017/9/26. 3 | */ 4 | const router = require('koa-router')() 5 | const path = require('path') 6 | const fs = require('fs') 7 | var exports = {} 8 | exports['auto'] = function (app) { 9 | let files = fs.readdirSync(path.join(__dirname, 'controllers')) 10 | 11 | let jsFiles = files.filter((f) => { 12 | return f.endsWith('.js') 13 | }, files) 14 | 15 | // 控制器文件 16 | for (let f of jsFiles) { 17 | console.log(`import controller from file ${f}...`) 18 | let name = f.substring(0, f.length - 3) 19 | exports[name] = require('./controllers/' + f) 20 | router.use('/' + name, exports[name].routes(), exports[name].allowedMethods()) 21 | app.use(exports[name].routes(), exports[name].allowedMethods()) 22 | } 23 | } 24 | module.exports = exports 25 | -------------------------------------------------------------------------------- /client/layouts/error.vue: -------------------------------------------------------------------------------- 1 | 15 | 20 | 21 | 45 | -------------------------------------------------------------------------------- /client/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 52 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /* 3 | ** Headers of the page 4 | */ 5 | head: { 6 | title: 'starter', 7 | meta: [ 8 | { charset: 'utf-8' }, 9 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 10 | { hid: 'description', name: 'description', content: 'Nuxt.js project' } 11 | ], 12 | link: [ 13 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } 14 | ] 15 | }, 16 | /* 17 | ** Global CSS 18 | */ 19 | css: ['~assets/css/main.css'], 20 | /* 21 | ** Customize the progress-bar color 22 | */ 23 | loading: { color: '#3B8070' }, 24 | /* 25 | ** Add axios globally 26 | */ 27 | build: { 28 | vendor: ['axios'], 29 | /* 30 | ** Run ESLINT on save 31 | */ 32 | extend (config, ctx) { 33 | if (ctx.isClient) { 34 | config.module.rules.push({ 35 | enforce: 'pre', 36 | test: /\.(js|vue)$/, 37 | loader: 'eslint-loader', 38 | exclude: /(node_modules)/ 39 | }) 40 | } 41 | } 42 | }, 43 | cache: true, 44 | srcDir: 'client/', 45 | router: { 46 | middleware: 'auth' 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/pages/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 42 | 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2_koa2_nuxt_ssr", 3 | "version": "1.0.0", 4 | "description": "A vue project with koa2 and nuxt for ssr.", 5 | "author": "Yipeng <228331207@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "cross-env NODE_ENV=development node buildconf && backpack dev", 9 | "alpha": "cross-env NODE_ENV=alpha node buildconf && nuxt build && backpack build", 10 | "uat": "cross-env NODE_ENV=uat node buildconf && nuxt build && backpack build", 11 | "build": "cross-env NODE_ENV=production node buildconf && nuxt build && backpack build", 12 | "start": "cross-env NODE_ENV=production node build/main.js", 13 | "precommit": "npm run lint", 14 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore ." 15 | }, 16 | "dependencies": { 17 | "axios": "^0.16.2", 18 | "cross-env": "^5.0.1", 19 | "koa": "^2.3.0", 20 | "koa-router": "^7.2.1", 21 | "nuxt": "^1.0.0-rc3", 22 | "source-map-support": "^0.4.15" 23 | }, 24 | "devDependencies": { 25 | "babel-eslint": "^7.1.1", 26 | "koa-log4": "^2.2.1", 27 | "backpack-core": "^0.3.0", 28 | "eslint": "^3.13.1", 29 | "eslint-config-standard": "^10.2.1", 30 | "eslint-loader": "^1.9.0", 31 | "eslint-plugin-html": "^2.0.3", 32 | "eslint-plugin-import": "^2.2.0", 33 | "eslint-plugin-node": "^4.2.2", 34 | "eslint-plugin-promise": "^3.4.0", 35 | "eslint-plugin-standard": "^3.0.1", 36 | "nodemon": "^1.11.0", 37 | "scmp": "^2.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /helper/services.js: -------------------------------------------------------------------------------- 1 | import {apiPath} from '../config/app' 2 | import axios from 'axios' 3 | import utils from './utils' 4 | 5 | /** 6 | * 构造接口请求方法 7 | * @author wangyipeng 8 | */ 9 | let services = {} 10 | 11 | for (let i in apiPath) { 12 | let hostApiPath = apiPath[i] 13 | let apiHost = hostApiPath['host'] 14 | services[i] = {} 15 | for (let ind in hostApiPath) { 16 | if (ind === 'host') { 17 | continue 18 | } 19 | let api = hostApiPath[ind] 20 | services[i][ind] = async function (params, isNeedStatus = false) { 21 | let apiUrl = api.url 22 | let newParams = {} 23 | if (params) { 24 | utils.each(params, function (ind, param) { 25 | if (apiUrl.indexOf('{' + ind + '}') > -1) { 26 | apiUrl = apiUrl.replace('{' + ind + '}', param) 27 | } else { 28 | newParams[ind] = param 29 | } 30 | }) 31 | } 32 | let data = newParams 33 | let config = {} 34 | let response = {} 35 | if (api.method === 'put' || api.method === 'post' || api.method === 'patch') { 36 | response = await axios[api.method](apiHost + apiUrl, data, config) 37 | if (!isNeedStatus) { 38 | response = response.data 39 | } 40 | } else { 41 | config.params = newParams 42 | response = (await axios[api.method](apiHost + apiUrl, config)) 43 | if (!isNeedStatus) { 44 | response = response.data 45 | } 46 | } 47 | return response 48 | } 49 | } 50 | } 51 | 52 | export default services 53 | -------------------------------------------------------------------------------- /helper/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 辅助函数 3 | * @author wangyipeng 4 | */ 5 | export default { 6 | isFunction (fn) { 7 | return Object.prototype.toString.call(fn) === '[object Function]' 8 | }, 9 | /** 10 | *@param {Object}|{Array} object 需要遍历处理的对象或数组 11 | *@param {Function} callback 遍历处理回调函数 12 | *@param {Array} args callback回调函数的附加参数 13 | */ 14 | each (object, callback, args) { 15 | let name 16 | let i = 0 17 | let length = object.length 18 | let isObj = length === undefined || this.isFunction(object) 19 | if (args) { 20 | if (isObj) { 21 | for (name in object) { 22 | if (callback.apply(object[name], args) === false) { 23 | break 24 | } 25 | } 26 | } else { 27 | for (; i < length;) { 28 | if (callback.apply(object[i++], args) === false) { 29 | break 30 | } 31 | } 32 | } 33 | } else { 34 | if (isObj) { 35 | for (name in object) { 36 | if (callback.call(object[name], name, object[name]) === false) { 37 | break 38 | } 39 | } 40 | } else { 41 | for (let value = object[0]; i < length && callback.call(value, i, value) !== false; value = object[++i]) { 42 | } 43 | } 44 | } 45 | return object 46 | }, 47 | date2String (date) { 48 | let year = date.getFullYear() 49 | let month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1) 50 | let day = (date.getDate() + 1) < 10 ? '0' + (date.getDate() + 1) : (date.getDate() + 1) 51 | return year + '-' + month + '-' + day 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/pages/about.vue: -------------------------------------------------------------------------------- 1 | 25 | 57 | 73 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import path from 'path' 3 | import Log4js from 'koa-log4' 4 | import AutoRoutes from './autoRoutes' 5 | import logConfig from '../config/log4js' 6 | 7 | import {Nuxt, Builder} from 'nuxt' 8 | const app = new Koa() 9 | const logger = Log4js.getLogger('app') 10 | const host = process.env.HOST || '127.0.0.1' 11 | const port = process.env.PORT || 3000 12 | 13 | // Import and Set Nuxt.js options 14 | let config = require('../nuxt.config.js') 15 | config.dev = !(app.env === 'production') 16 | 17 | // 生成logs目录 && 加载配置文件 start 18 | const logPath = path.join(__dirname, 'logs') 19 | try { 20 | require('fs').mkdirSync(logPath) 21 | } catch (err) { 22 | if (err.code !== 'EEXIST') { 23 | console.error('Could not set up log directory, error was: ', err) 24 | process.exit(1) 25 | } 26 | } 27 | Log4js.configure(logConfig, {cwd: logPath}) 28 | // 生成logs目录 && 加载配置文件 end 29 | 30 | if (!config.dev) { 31 | app.use(Log4js.koaLogger(Log4js.getLogger('http'), {level: 'auto'})) 32 | } 33 | 34 | // Instantiate nuxt.js 35 | const nuxt = new Nuxt(config) 36 | 37 | // Build in development 38 | if (config.dev) { 39 | const builder = new Builder(nuxt) 40 | builder.build().catch(e => { 41 | console.error(e) // eslint-disable-line no-console 42 | process.exit(1) 43 | }) 44 | } 45 | 46 | app.use(async (ctx, next) => { 47 | ctx.Log4js = Log4js 48 | await next() 49 | }) 50 | 51 | AutoRoutes.auto(app) 52 | 53 | app.use(ctx => { 54 | ctx.status = 200 // koa defaults to 404 when it sees that status is unset 55 | return new Promise((resolve, reject) => { 56 | ctx.res.on('close', resolve) 57 | ctx.res.on('finish', resolve) 58 | nuxt.render(ctx.req, ctx.res, promise => { 59 | // nuxt.render passes a rejected promise into callback on error. 60 | promise.then(resolve).catch(reject) 61 | }) 62 | }) 63 | }) 64 | 65 | app.on('error', function (err, ctx) { 66 | logger.error('server error', err, ctx) 67 | }) 68 | 69 | app.listen(port, host) 70 | console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console 71 | --------------------------------------------------------------------------------