Search code examples
next.jsstatic-site

Launch-time initialization in Next.js static/exported site


I'm trying to use Next to power an Electron app. electron-next uses Next's static site mode for its production build, which calls getInitialProps at build-time, rather than launch-time.

start.js (initially rendered page)

import Link from 'next/link'

export default function Start({date}) {
  return (
    <div>
      <div>Date is {date}</div> {/* <- will always be the build time */}
      <Link href="/about">
        <a>Take me to the About page</a>
      </Link>
    </div>
  )
}

Start.getInitialProps = () => {
  return {
    date: "" + new Date()
  }
}

Interestingly, using Link to navigate elsewhere does, in fact, result in a dynamic getInitialProps call.

about.js

import Link from 'next/link'

export default function About({date}) {
  return (
    <div>
      <div>Date is {date}</div> {/* <- will be the time the link was clicked */}
      <div>Important info about this app</div>
    </div>
  )
}

About.getInitialProps = () => {
  return {
    date: "" + new Date()
  }
}

Is there a non-hacky way to get dynamic behavior for the initial route? I imagine this would have plenty of use cases in static sites, too.


Solution

  • I ended up not using getInitialProps at all. Instead, I'm using a React hook. It works basically like this:

    async function useModel() {
      const modelRef = useRef(null)
    
    
      // This hook will render at build-time by Next.js's static site, in which
      // case the conditional loading of the model will never happen.
      //
      // At startup-time, it will be re-renderered on the Electron renderer thread,
      // at which time, we'll actually want to load data.
      if (process.browser && !modelRef.current) {
        const m = new Model()
        await m.init() // <- Assumed to have some async fetching logic
        modelRef.current = m
      }
    
      return modelRef.current
    }
    

    Then, the top-level component can easily use the presence of the model to determine what to do next:

    function Start() {
      const model = useModel()
    
      if (!model) {
        return <div>Loading...</div>
      } else {
        return <MyProperUI model={model} />
      }
    }
    

    Or, you could easily rig it up to show an unpopulated default UI, or whatever.

    So basically, use getInitialProps for code you want to run exactly once, server-side/build-time or client-side. Otherwise, use other means of initialization. As seen here, hooks allow for this with pretty minimal boilerplate.