Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NextJS consistently access request object for every page

I'm using express + passport + nextjs to set up an app that will perform authentication using OpenID Connect. The user data is stored on the request object using express-session which gives me req.user on every request as usual.

Now I want to pass the user information to the front-end so that I can use it for something, but there does not seem to be any consistent way to do this for all requests. I can use getServerSideProps for individual pages, but not for every page through either _document or _app. How can I set this up?

Here is my current _document.tsx

import Document, {
  Head,
  Main,
  NextScript,
  DocumentContext,
} from "next/document"

export default class Doc extends Document {
  public static async getInitialProps(ctx: DocumentContext) {
    const req: any = ctx.req
    console.log("req/user", `${!!req}/${!!(req && req.user)}`)
    const initialProps = await Document.getInitialProps(ctx)
    return {
      ...initialProps,
      user: req?.user || "no user",
    }
  }

  public render() {
    return (
      <html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

It appears to return a request object only during the very first request, not any subsequent refreshes of the page.

I've created a small repo that reproduces the issue here: https://github.com/rudfoss/next-server-custom-req

It seems ridiculous that there is no way to do this for all pages in an easy manner.

Edit: For reference this is my server.js. It is the only other relevant file in the repo

const express = require("express")
const next = require("next")

const dev = process.env.NODE_ENV !== "production"

const start = async () => {
  console.log("booting...")
  const server = express()
  const app = next({ dev, dir: __dirname })
  const handle = app.getRequestHandler()
  await app.prepare()

  server.use((req, res, next) => {
    req.user = {
      authenticated: false,
      name: "John Doe",
    }
    next()
  })

  server.get("*", handle)

  server.listen(3000, (err) => {
    if (err) {
      console.error(err)
      process.exit(1)
    }

    console.log("ready")
  })
}

start().catch((error) => {
  console.error(error)
  process.exit(1)
})
like image 547
Thomas Avatar asked Jun 08 '20 10:06

Thomas


1 Answers

It is recommended to do this via function components, as seen in the Next.js custom App docs:

// /pages/_app.tsx
import App, { AppProps, AppContext } from 'next/app'

export default function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

MyApp.getInitialProps = async (appContext: AppContext) => {
  // calls page's `getInitialProps` and fills `appProps.pageProps`
  const appProps = await App.getInitialProps(appContext)
  const req = appContext.ctx.req

  return {
    pageProps: {
      ...appProps.pageProps,
      user: req?.user,
    },
  }
}

As in your answer, this will run on every request though so automatic static optimization will not be active.

Try a demo of changing pageProps in MyApp.getInitialProps (without usage of req.user) on the following CodeSandbox:

https://codesandbox.io/s/competent-thompson-l9r1u?file=/pages/_app.js

like image 118
Karl Horky Avatar answered Oct 13 '22 13:10

Karl Horky