Thomas
Thomas

Reputation: 1851

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)
})

Upvotes: 9

Views: 13660

Answers (3)

Vitthal Kulkarni
Vitthal Kulkarni

Reputation: 85

I also had the similar problem where I had to fetch loggedIn user details from my Auth api. I solved it by wrapping my whole app inside a context provider, then using a set function for the initialState, which will remember if it was called before and fetch user details only once. Then in my each page, wherever I require these user details, I used the context to see if details are available and call the set function if details are not available. This way I think I achieved:

  1. Only one request to fetch user details
  2. Because it happens from the client side, TTFB is better
  3. I can still take advantage of getStaticProps and getServerSideProps where it is required.

Upvotes: 0

Thomas
Thomas

Reputation: 1851

Turns out I can override getInitialProps on _app to make this work:

class MyApp extends App {
  public static async getInitialProps({
    ctx
  }: AppContext): Promise<AppInitialProps> {
    const req: any = ctx.req
    return {
      pageProps: {
        user: req?.user
      }
    }
  }

  public render() {
    //...
  }
}

This will run on every request though so static optimization will not work, but in my case I need the information so I'm willing to accept the trade-off.

Edit: This answer also works, but it uses the "old" class-based component syntax which is no longer recommended. See answer from Karl for a more modern version using functional-component syntax.

Upvotes: 0

Karl Horky
Karl Horky

Reputation: 4944

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

Upvotes: 12

Related Questions