這是一個基礎demo,由開發者本身提供server,用於渲染html
const Koa = require('koa') const Router = require('koa-router') const next = require('next') // 建立實例 const app = next({ dev, conf, dir: './src' }) app.prepare().then(() => { const server = new Koa() const router = new Router() router.get('/', async ctx => { // 渲染 await app.render(ctx.req, ctx.res) ctx.respond = false }) server.listen(port) })
在自定義服務端中經過const app = next()
建立實例並使用app.render(req, res)
方法進行渲染react
因此直接從app.render這個渲染入口開始着手瀏覽器
瞭解框架邏輯惟一的方式就是看源碼,因爲源碼過於細節,下面我會簡化涉及到的代碼,僅保留主要邏輯,附帶具體地址,有興趣深刻的同窗能夠看看
next-server/server/next-server.ts
app
import { renderToHTML } from './render.tsx' // app.render入口函數 this.render(req, res){ const html = await this.renderToHTML(req, res) return this.sendHTML(req, res, html) } this.renderToHTML(req, res){ const html = await this.renderToHTMLWithComponents(req, res) return html } this.renderToHTMLWithComponents(req, res) { // render內的renderToHTML return renderToHTML(req, res) }
能夠看到上面都是簡單的調用關係,雖然刪除了大部分代碼,但咱們只須要知道,最終它調用了render.tsx
內的renderToHTML
框架
這是一個至關長的函數,也就是本篇文章的主要內容,經過renderToHTML
可以瞭解到大部份內容,和上面相同,刪除了大部分邏輯,僅保留核心代碼dom
// next-server/server/render.tsx function renderToHTML(req, res) { // 參考下文#補充 loadGetInitialProps,很是簡單的函數,就是調用了_app.getInitialProps // _app.getInitialProps函數內部會先調用pages.Component的getInitialProps // 也就是在這裏,咱們編寫的組件內的getInitialProps一樣會被調用,獲取部分初始數據 let props = await loadGetInitialProps(App, { Component, router, ctx }); // 定義渲染函數,返回html和head const renderPage = () => { // 參考下文#補充 render return render( renderToStaticMarkup, //渲染_app,以及其內部的pages.Component也就是咱們編寫的代碼,詳情參考next/pages/_app.tsx <App Component={EnhancedComponent} router={router} {...props} /> ); }; // _document.getInitialProps會調用renderPage,渲染_app也就是咱們的正常開發時編寫的組件代碼,詳情參考next/pages/_app.tsx const docProps = await loadGetInitialProps(Document, { ...ctx, renderPage }); // 參考下文#補充 renderDocument let html = renderDocument(Document, { props, docProps, }); return html; }
req=> render(req, res) renderToHTML(req, res) renderToHTMLWithComponents(req, res) renderToHTML(req,res) _app.initialProps = loadGetInitialProps(App, { Component, router, ctx }) _document.initialProps = loadGetInitialProps(Document, { ...ctx, renderPage }) renderDocument(Document, _app.initialProps, _document.initialProps) <=res
對應koa
req=> _app.getInitialProps() Component.getInitialProps() _document.getInitialProps() _app.render() Component.render() _document.render() <=res
這篇文章簡要描述next服務端的渲染過程,從中咱們也能清楚_document、_app、以及pages內本身編寫的組件之間的關係...async
要是還沒明白,請從新閱讀一遍renderToHTML函數內的註釋內容ide
須要注意的一些點,隨緣補充函數
上文中出現的部分函數,直接截取自源碼,都相對簡單,能夠做爲參考
function render( renderElementToString: (element: React.ReactElement<any>) => string, element: React.ReactElement<any>, ampMode: any, ): { html: string; head: React.ReactElement[] } { let html let head try { html = renderElementToString(element) } finally { head = Head.rewind() || defaultHead(undefined, isAmp(ampMode)) } return { html, head } }
export async function loadGetInitialProps<C extends BaseContext, IP = {}, P = {}>(Component: NextComponentType<C, IP, P>, ctx: C): Promise<IP | null> { if (process.env.NODE_ENV !== 'production') { if (Component.prototype && Component.prototype.getInitialProps) { const message = `"${getDisplayName(Component)}.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.` throw new Error(message) } } // when called from _app `ctx` is nested in `ctx` const res = ctx.res || (ctx.ctx && ctx.ctx.res) if (!Component.getInitialProps) { return null } const props = await Component.getInitialProps(ctx) if (res && isResSent(res)) { return props }
function renderDocument( Document: DocumentType, {...不少參數,太長省略} ): string { return ( '<!DOCTYPE html>' + renderToStaticMarkup( <AmpModeContext.Provider value={ampMode}> <Document __NEXT_DATA__={{ dataManager: dataManagerData, props, // The result of getInitialProps page: pathname, // The rendered page query, // querystring parsed / passed by the user buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles dynamicBuildId, // Specifies if the buildId should by dynamically fetched assetPrefix: assetPrefix === '' ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML nextExport, // If this is a page exported by `next export` dynamicIds: dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds, err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML }} dangerousAsPath={dangerousAsPath} ampPath={ampPath} amphtml={amphtml} hasAmp={hasAmp} staticMarkup={staticMarkup} devFiles={devFiles} files={files} dynamicImports={dynamicImports} assetPrefix={assetPrefix} {...docProps} /> </AmpModeContext.Provider>, ) ) }