github地址:github完整代碼
egg 初始化
首先全局安裝egg-init命令瞒斩,在本地新建文件夾粹懒,進(jìn)入文件夾執(zhí)行初始化命令。
npm install egg-init -g
mkdir eggNext && cd eggNext
egg-init --type=simple
npm install
npm run dev
初始化目錄關(guān)鍵結(jié)構(gòu)如下所示
+ app
+ controller
|- home.js
router.js
+ config
|-config.default.js
|-plugin.js
- app目錄就是egg服務(wù)的項(xiàng)目項(xiàng)目,其中router文件用以指定path對應(yīng)的處理函數(shù)堪澎,controller中就是path相關(guān)的處理函數(shù)。
可以看到在router中把/路徑指向了controller中home文件來進(jìn)行處理味滞。
后續(xù)啟動next項(xiàng)目時(shí)樱蛤,就需要在這里增加路徑
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
- config用以存放相關(guān)的配置文件, 完整配置應(yīng)該是有如下環(huán)境的區(qū)分的,默認(rèn)生成的項(xiàng)目目錄里只有config.default.js剑鞍。后續(xù)我們也會增加配置配置文件來對next進(jìn)行一些配置昨凡。
+ config
|- config.default.js
|- config.prod.js
`- config.local.js
next 初始化
全局安裝next react react-dom
npm install next react react-dom --save
egg + next
我們不采用next自帶的啟動方式,而是采用egg.js啟動next的方式蚁署。
參考next文檔中自定義服務(wù)器中的demo便脊,
// server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
雖然服務(wù)端不是采用的egg.js的方式,但是我們可以提取出自定義服務(wù)器啟動next最關(guān)鍵的部分光戈。
const next = require('next');
app = next(config);
app.render(req, res, '/path', query);
-
傳入config參數(shù)對next進(jìn)行配置哪痰,可配置參數(shù)如下,
config參數(shù).png - 使用render函數(shù)來渲染對應(yīng)的頁面田度。/path對應(yīng)1中dir next項(xiàng)目下的pages里的路徑妒御。
根據(jù)以上分析,我們進(jìn)行如下操作
- 我們在 app的config下镇饺,創(chuàng)建config.local.js乎莉,config.beta.js, config.prod.js奸笤,增加如下配置惋啃,后續(xù)用來配置next參數(shù)。
module.exports = appInfo => {
const config = {};
config.next = {
dev: true, // config.prod.js中設(shè)置為false
dir: './ssr',
};
return {
...config,
};
};
- 創(chuàng)建srr/pages/index.js监右,這就是我們的next的頁面啦
// ssr/pages/index.js
export default function Home() {
return (
<div>
test
</div>
)
}
上述這些準(zhǔn)備工作做好了边灭,最最難的復(fù)雜的一點(diǎn)來了,我們?nèi)绾蝸碛胑gg啟動next項(xiàng)目呢健盒。
- 首先我們通過extend/application.js將next擴(kuò)展到app上绒瘦,在app目錄下新建extend/application.js 【 egg.js extend參考文檔】,之后你在app目錄下通過app.next就可以訪問到我們配置好的next了~
'use strict';
const Next = require('next');
const NEXT = Symbol('Application#next');
module.exports = {
get next() {
if (!this[NEXT]) {
this[NEXT] = Next(this.config.next);
}
return this[NEXT];
},
};
- 在router扣癣、controller里建立路徑的的處理函數(shù)惰帽,使用app.next.render來渲染next的頁面
// router.js
router.get('/ssr/*', controller.ssr.index);
// controller/ssr.js
const Controller = require('egg').Controller;
class SSRController extends Controller {
async index() {
const { ctx, app } = this;
// ctx.body = 'hi, egg';
const { req, res } = ctx;
ctx.body = await app.next.render(req, res, '/');
}
}
module.exports = SSRController;
滿心歡喜啟動....結(jié)果報(bào)了如下錯誤。??
我們重新回到next的自定義服務(wù)器啟動的demo父虑「眯铮可以看出,啟動服務(wù)器是在next準(zhǔn)備好了之后,才會啟動服務(wù)呜魄,而我們項(xiàng)目里是沒有等到next加載好服務(wù)就已經(jīng)啟動悔叽,導(dǎo)致資源加載可能存在問題。
// demo
app.prepare().then(() => {
createServer(() => {})
})
大刀闊斧進(jìn)行再來改進(jìn)爵嗅!egg雖然幫我們把啟動都封裝好了娇澎,但是我們也可以進(jìn)行自定義啟動,我們在項(xiàng)目目錄下建立app.js操骡,寫入以下代碼九火,保證next prepare成功后再啟動服務(wù)【egg自啟動】
// app.js
'use strict';
module.exports = app => {
// NOTE: 這里一定要等next prepare好再啟動服務(wù)
app.next.prepare().then(() => {
app.beforeStart(() => {
process.on('unhandledRejection', (reason, p) => {
});
process.on('uncaughtException', reason => {
});
});
});
};
很好,我們再啟動一次册招! 這次頁面沒有報(bào)錯岔激,只是空白,打開控制臺是掰,發(fā)現(xiàn)請求了很多js資源虑鼎,都沒有找到。
可以看到這些js資源全部都是_next目錄下的键痛,而我們的項(xiàng)目里沒有處理_next路徑炫彩。
其實(shí)是next在啟動的時(shí)候會在ssr項(xiàng)目目錄下生成.next目錄,回到demo絮短,next里其實(shí)是handle這些_next請求的方法的江兢,當(dāng)請求路徑不滿足其他指定路徑時(shí),會把路徑交給handle來處理丁频。
const handle = app.getRequestHandler()
else {
handle(req, res, parsedUrl)
}
根據(jù)這個(gè)杉允,我們增加一個(gè)中間件,讓路由先通過handle函數(shù)處理一下席里。
1 在app目錄下增加middleware文件夾叔磷,新建ssr.js
'use strict';
const { parse } = require('url');
module.exports = (options, app) => async (ctx, next) => {
const { path, req, res } = ctx;
if (/\/_next\//.test(path)) {
const parsedUrl = parse(req.url, true);
ctx.status = 200;
if (/\.js$/.test(path)) {
ctx.set('Content-Type', 'application/javascript');
}
if (/\.css$/.test(path)) {
ctx.set('Content-Type', 'text/css');
}
const handle = app.next.getRequestHandler();
await handle(req, res, parsedUrl);
}
await next();
}
- 在config.default.js里增加中間件配置
config.middleware = ['ssr'];
再次啟動項(xiàng)目~ 成功啦~~