What is proper way to detect device in Next.js SSR?

Viewed 10208

I have <MobileLayout />, <DesktopLayout />. I'm using Next.js for Server Side Rendering. And I noticed there are many famous ui library has mobile detection components like <Respnosive /> component in Semantic-UI-React. But all of this is client side method, not working properly on SSR

I read some documents the conclusion is I should check user-agent of server side req.headers. In Next.js, What is proper way to detect device and conditonally render one of MobileLayout / DesktopLayout?

What I tried

in _app.js

import isMobile from 'ismobilejs'

...

function Homepage({ Component, pageProps, mobile }){
  return (
    mobile ? 
      <MobileLayout><Component {...pageProps} /></MobileLayout> : 
      <DesktopLayout><Component {...pageProps} /></DesktopLayout>
  )
}

HomePage.getInitialProps = async (appContext) => {
  const userAgent = appContext.ctx.req.headers['user-agent']
  const mobile = isMobile(userAgent).any
  const appProps = await App.getInitialProps(appContext)
  return { ...appProps, mobile }
}

But the problem is getIntialProps on _app.js executed every page load. with moving page with client, the appContext.ctx is undefined so it will omit error. and I think this method might block some nextjs builtin optimizations.

Error in error page getInitialProps: TypeError: Cannot read property 'headers' of undefined

So what is propery way to check device in Next.js?

2 Answers

If you want to detect the user's device using userAgent, your best bet is this answer:

IndexPage.getInitialProps = ({ req }) => {
  let userAgent;
  if (req) { // if you are on the server and you get a 'req' property from your context
    userAgent = req.headers['user-agent'] // get the user-agent from the headers
  } else {
    userAgent = navigator.userAgent // if you are on the client you can access the navigator from the window object
  }
}

(Note you should actually be using getServerSideProps or getStaticProps when possible, if you have Next 9.3 or newer, but sometimes there is no replacement for the getInitialProps functionality.)

However, the folks at Mozilla advise:

It's worth re-iterating: it's very rarely a good idea to use user agent sniffing. You can almost always find a better, more broadly compatible way to solve your problem!

The maker of the isMobile package you're importing even warns:

You might not need this library. In most cases, responsive design solves the problem of controlling how to render things across different screen sizes.

So, see if you can use CSS3 media queries to conditionally render certain elements or change their size, etc., rather than having completely separate mobile and desktop layout components. But it's possible you have an edge case where you can't make any alternative option work.

If you are going to keep your current setup and use your two layouts on other pages, you might consider combining them into a parent <Layout> component that conditionally renders one or the other so you don't have to copy that logic into every page:

export const Layout = (props) => {
      return (
          props.mobile ? 
          <MobileLayout>{props.children}</MobileLayout> :
          <DesktopLayout>{props.children}</DesktopLayout>
      )
}
Related