Skip to content

Instantly share code, notes, and snippets.

@hi-ogawa
Last active June 3, 2022 04:03
Show Gist options
  • Select an option

  • Save hi-ogawa/5b3dd93d5abf4e395f785c20c6d773e0 to your computer and use it in GitHub Desktop.

Select an option

Save hi-ogawa/5b3dd93d5abf4e395f785c20c6d773e0 to your computer and use it in GitHub Desktop.
Reading Next.js

Reading Next.js

TODO

  • server handler
    • API
    • SSR
  • client initialization
  • middleware
  • client navigation, prefetch, and data request
  • production build
    • manifest generation
    • webpack bundle
  • swc, babel
    • transform plugins
  • dev server
    • react fast refresh

Tips

  • Build and Test

Cf. https://github.com/vercel/next.js/blob/canary/contributing.md

# (re)build only "next" package
yarn lerna run prepublish --scope 'next'

# Run unit test
yarn jest --findRelatedTests test/unit/link-rendering.test.ts

# Run integration test with headless mode
HEADLESS=true yarn jest --findRelatedTests test/integration/middleware/core/test/index.test.js

# Run integration test app
yarn next test/integration/middleware/core/

# Build app with printing trace
TRACE_TARGET=CONSOLE yarn next build ...
  • Grep
    • include: packages, test
    • exclude: **/dist, **/compiled, **/.next

Code flow

  • Build
build (next/build/index.ts) =>
  generateBuildId
  verifyTypeScriptSetup => require("typescript"), runTypeCheck
  verifyAndLint
  collectPages
  createPagesMapping
  createEntrypoints =>
    For each page, specify webpack entry file
    e.g. "next-middleware-loader", "next-client-pages-loader", etc... or page js file itself
  routesManifest
  mkdir(".next")
  writeFile(".next/routes-manifest.json", ...)
  getBaseWebpackConfig for each entrypoint (client and server) =>
    (choose "next-swc-loader" or `nextBabelLoader`)
    (define webpack.Configuration)
      handleExternals (e.g. not to bundle node_modules in server pages)
    buildConfiguration (css loader etc...)
    (run webpack override from next.config.js)
  runCompiler (client and server)
  (obtain `pageInfo` etc.. by statically checking built artifacts)
  (if `outputFileTracing` create .nft.json for each page and next-server itself)
  exportApp (static generation) =>
    exportPage (Imitate SSR using loadComponents, renderToHTML, etc...)
  writeFile (for a bunch of manifests, e.g. middleware, images, etc...)
  • Server
[Server initialization]
NextServer.createServer =>
  new Server (from "./next-servert.ts") or
  new DevServer (from "./dev/next-dev-server.ts") =>
    Server.constructor =>
      (when production)
        require(pagesManifestPath)
        require(middlewareManifestPath)
      generateRoutes =>
        routes for middleware, render, etc...
      new Router

DevServer.prepare =>
  new HotReloader => ??
  startWatcher => ??


[Server handler (via NextServer.getRequestHandler)]
Server.handleRequest =>
  run => Router.execute


[SSR (via server router)]
Server.render => renderToResponse =>
  # fallback to `renderErrorToResponse` when this routine throws
  findPageComponents => loadComponents => requirePage => require
  renderToResponseWithComponents =>
    (doRender) => renderToHTML =>
      new ServerRouter (for SSR router context)
      getServerSideProps
      (renderDocument) =>
        loadGetInitialProps(Document, ...) =>
          (defaultGetInitialProps) => (renderPage) =>
            ReactDOMServer.renderToString(<AppContainer>...)
      ReactDOMServer.renderToStaticMarkup(... <Document> ...) =>
        <Main> => <div id="__next">__NEXT_BODY_RENDER_TARGET__</div>
        <NextScript> => <script id="__NEXT_DATA__">
      (replace "__NEXT_BODY_RENDER_TARGET__" with html from `renderToString` above)


[API (via server router)]
Server.handleApiRequest =>
  getPagePath(page)
  require
  apiResolver => (module).default


[Middleware (via server router)]
catchAllMiddleware.fn =>
  Server.runMiddleware =>
    (for each matching middleware) sandbox.run => (module).default
    break if not "x-middleware-next"
  • Client
[Client initialization]
"main.js" chunk (CLIENT_STATIC_FILES_RUNTIME_MAIN in getBaseWebpackConfig) =>
  (client/next.js or client/next-dev.js) =>
    (client/index.tsx) =>
      document.getElementById('__NEXT_DATA__') (e.g. hydrateProps, page, query, etc...)
      new PageLoader => createRouteLoader
      RouteLoader.onEntrypoint for each "window.__NEXT_P" =>
        (cf. webpack/loaders/next-client-pages-loader.ts)
        entrypoint[...].set(require(...))
    initNext =>
      RouteLoader.whenEntrypoint (for client side `App` and `Component`)
      createRouter
      render => doRender =>
        renderReactElement =>
          # AppContainer has `componentDidCatch`, which in turn triggers `renderError`
          ReactDOM.hydrate(document.getElementById('__next'), <AppContainer> ...)


[Client navigation (Link, Router, etc...)]
useEffect(isVisible) or onMouseEnter => prefetch =>
  Router.prefetch =>
    PageLoader.getPageList => getClientBuildManifest
    _preflightRequest =>
      PageLoader.getMiddlewareList
      (if matching middleware) _getPreflightData (and cache results)
    PageLoader.prefetch => RouteLoader.prefetch => loadRoute =>
      getFilesForRoute (lookup manifest for js/css file names used in the page)
      maybeExecuteScript
      fetchStyleSheet
      (and cache results)
    (if ssg url)
      PageLoader.getDataHref (aka. "/_next/data/???.json")
      fetchNextData

onClick => linkClicked => Router.push => Router.change =>
  _preflightRequest
  getRouteInfo =>
    fetchComponent => PageLoader.loadPage => RouteLoader.loadRoute
    getDataHref, fetchNextData (similar routines as prefetch above)
  changeState (if relace, update window.history)
  set => notify => subscription ~~> render (cf. createRouter in client./index.tsx)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment