Exploring Undocumented getInitialProps Properties on Next.js

An adventure log on discovering the secrets of the getInitialProps function and its mysterious object properties — updated March 2020

Published at

Introduction 👋

Sometime around 2018, I was working on a web app using Next.js on an older version. At the time, one of the features of Next.js is that it supports initial data population using the getInitialProps static method, which means that you can populate the page component props before loading the page (e.g. fetching news feeds).

The latest docs for Next.js is available on their official website. At version 9.1.7 and before, the docs was published on GitHub. Specifically at the "Fetching data and component lifecycle" section, it shows how to use getInitialProps and what parameters that can be destructured. A snippet from their readme:

getInitialProps receives a context object with the following properties:

  • pathname - path section of URL
  • query - query string section of URL parsed as an object
  • asPath - String of the actual path (including the query) shows in the browser
  • req - HTTP request object (server only)
  • res - HTTP response object (server only)
  • err - Error object if any error is encountered during the rendering

Before updating this post, getInitialProps has one additional property which you can read on their readme at version 8.0.0 and below:

jsonPageRes - Fetch Response object (client only)

Pretty straightforward, right? Except for one minor problem.

Something's not right 🔥

On another part of the readme which explains on how to use a custom App component on _app.js, it also uses getInitialProps but with different destructured context parameters. Here's the code snippet from the readme:

static async getInitialProps({ Component, router, ctx }) {
  let pageProps = {}

  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx)
  }

  return { pageProps }
}

From the snippet above, it shows that getInitialProps doesn't use the documented object properties. And also it seems that I'm not the only one confused about this. Quoting from a discussion on a Spectrum thread,

What damn are and where are coming from the following properties of the context parameter?

So for many weeks, I searched the codebase, issues, and even Spectrum threads related to getInitialProps. And in this post I will try my best to explain the getInitialProps debacle.

Inspecting and dumping ✨

In another Spectrum thread that I created, @revskill recommends using util.inspect to analyze objects. So I made a temporary page (pages/temp.js) and use this snippet below to dump the getInitialProps parameter using util.inspect (note that this is Next.js before version 9):

import React, { Component } from "react";
import { inspect } from "util";

export default class extends Component {
  static getInitialProps(something) {
    return {
      pageLog: inspect(something, true, 0),
    };
  }

  componentDidMount() {
    console.log(this.props.pageLog);
  }

  render() {
    return <pre>Check the console ^^</pre>;
  }
}

Checking the console, it returns this:

{ err: undefined,
  req: [Object],
  res: [Object],
  pathname: '/',
  query: {},
  asPath: '/' }

All properties shown are already documented on the readme, so where's Component, router, and ctx? Because the readme shows that those three properties are used on a custom App, so I made pages/_app.js and dump the parameter on getInitialProps like before (again, note that this is before version 9):

import App, { Container } from "next/app";
import React from "react";
import { inspect } from "util";

export default class MyApp extends App {
  static async getInitialProps(something) {
    const { Component, ctx } = something;
    let pageProps = {};

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }

    return {
      pageProps,
      appLog: inspect(something, true, 0),
    };
  }

  componentDidMount() {
    console.log(this.props.appLog);
  }

  render() {
    const { Component, pageProps } = this.props;

    return (
      <Container>
        <Component {...pageProps} />
      </Container>
    );
  }
}

Now the console returns two logs:

{ err: undefined,
  req: [Object],
  res: [Object],
  pathname: '/',
  query: {},
  asPath: '/' }

{ Component: [Object], router: [Object], ctx: [Object] }

As you can see on the snippet above, I destructured two properties: Component and ctx. So from my understanding is that the Component object is the page component that will be loaded (e.g. pages/index.js), and the ctx object is the App context (which explains why it has a router property). Note the if (Component.getInitialProps), it's quite obvious that what it does is checks whether the page component has a getInitialProps function to run.

So what that means is the getInitialProps parameter (or context) differs from a page component. But this doesn't explain another thing.

Type? What type? 🐴

I'm a sucker for object types, so it really bothers me when statically adding getInitialProps to an App or page component obviously doesn't give any hints on my editor. And after inspecting a lot above, at some point I asked myself, "does next has a @types package?" And they have! Why did I bother inspecting ony by one?

March 2020 update note: DefinitelyTyped has deprecated the Next.js typings since version 9 already includes its own TypeScript declaration file. You can view the deprecation pull request on GitHub, courtesy of Resi Respati.

After that sudden realization, I added the type package and checked whether it has an object or interface with the name 'context using the IntelliSense extensions on Visual Studio Code. Lo and behold, I found three interfaces 'context' related (remember that this is before version 9):

import { NextContext } from "next";
import { AppComponentContext, NextAppContext } from "next/app";

After finding those three, I tried type hinting the getInitialProps function on both _app.js and a page component, and the results was fantastic:

App context object properties
App context object properties
Page context object properties
Page context object properties

Much better! Now I have found out that it has a @types package, getting to know more about its type and contents is much easier.

Getting to know more 🚀

In Visual Studio Code, you can jump to the definition of the type by command or control clicking the variable like below:

Preview on control or command clicking the object type
Preview on control or command clicking the object type

From the GIF above, it opens the declaration files in the node_modules directory in node_modules/@types/next. Or you can view the file on the @types/next repository on GitHub. Here's a snippet from the declaration files for the context object on the App component (NextAppContext):

/**
 * Context passed to App.getInitialProps.
 * Component is dynamic - cannot infer type.
 *
 * @template Q Query object schema.
 */
export interface NextAppContext<Q extends DefaultQuery = DefaultQuery> {
  Component: NextComponentType<any, any, NextContext<Q>>;
  router: RouterProps<Q>;
  ctx: NextContext<Q>;
}

And here's the context declaration for page components (NextContext):

/**
 * Context object used in methods like `getInitialProps()`
 * https://github.com/zeit/next.js/blob/7.0.0/server/render.js#L97
 * https://github.com/zeit/next.js/blob/7.0.0/README.md#fetching-data-and-component-lifecycle
 *
 * @template Q Query object schema.
 */
interface NextContext<Q extends DefaultQuery = DefaultQuery> {
  /** path section of URL */
  pathname: string;
  /** query string section of URL parsed as an object */
  query: Q;
  /** String of the actual path (including the query) shows in the browser */
  asPath: string;
  /** HTTP request object (server only) */
  req?: http.IncomingMessage;
  /** HTTP response object (server only) */
  res?: http.ServerResponse;
  /** Fetch Response object (client only) - from https://developer.mozilla.org/en-US/docs/Web/API/Response */
  jsonPageRes?: NodeResponse;
  /** Error object if any error is encountered during the rendering */
  err?: Error;
}

That's much better than inspecting objects one by one. 😅


Hopefully this adventure log isn't that confusing, since getInitialProps is already confusing at the start. Thanks for reading, and happy coding! 👋🏻

This was originally posted at Medium on November 26th, 2018 with the same title and contents.