Benjamin Sx
Benjamin Sx

Reputation: 287

How to detect the device on React SSR App with Next.js?

on a web application I want to display two different Menu, one for the Mobile, one for the Desktop browser. I use Next.js application with server-side rendering and the library react-device-detect.

Here is the CodeSandox link.

import Link from "next/link";
import { BrowserView, MobileView } from "react-device-detect";

export default () => (
  <div>
    Hello World.{" "}
    <Link href="/about">
      <a>About</a>
    </Link>
    <BrowserView>
      <h1> This is rendered only in browser </h1>
    </BrowserView>
    <MobileView>
      <h1> This is rendered only on mobile </h1>
    </MobileView>
  </div>
);

If you open this in a browser and switch to mobile view and look the console you get this error:

Warning: Text content did not match. Server: " This is rendered only in browser " Client: " This is rendered only on mobile "

This happen because the rendering by the server detects a browser and on the client, he is a mobile device. The only workaround I found is to generate both and use the CSS like this:

.activeOnMobile {
  @media screen and (min-width: 800px) {
    display: none;
  }
}

.activeOnDesktop {
  @media screen and (max-width: 800px) {
    display: none;
  }
}

Instead of the library but I don't really like this method. Does someone know the good practice to handle devices type on an SSR app directly in the react code?

Upvotes: 49

Views: 136721

Answers (19)

Pra2win
Pra2win

Reputation: 63

In next js with App dir you can do like

import { headers } from "next/headers";

export const isMobileRequest = () => {
  const userAgent = headers().get("user-agent") || "";

  const mobileRegex =
    /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;

  const isMobile = mobileRegex.test(userAgent);
  return isMobile
};

Upvotes: 0

Gabo Esquivel
Gabo Esquivel

Reputation: 4012

import { NextRequest, NextResponse, userAgent } from 'next/server'
 
export function middleware(request: NextRequest) {
  const url = request.nextUrl
  const { device } = userAgent(request)
  const viewport = device.type === 'mobile' ? 'mobile' : 'desktop'
  url.searchParams.set('viewport', viewport)
  return NextResponse.rewrite(url)
}

https://nextjs.org/docs/app/api-reference/functions/userAgent

Upvotes: 2

Chris GW Green
Chris GW Green

Reputation: 1213

I understand this is a much answered question, but I've just addressed this issue and want to share my findings.

Scenario - In my case, I want to hide and show app store buttons/qr codes based on device.

Client Side Solution - I tried using react-device-detect for client components but relying on isMobile caused className issues with SSR unless using useEffect which caused a page flash and clunky experience.

Server Side Solution - As its possible to access the userAgent via middleware, I've added the os name to the request headers, which I then access in a server component (e.g OSWrapper).

export const middleware = async (request: NextRequest) => {
  const {
    os: { name },
    device: { type },
  } = userAgent(request)

  ...
 
  const requestHeaders = new Headers(request.headers)
  name && requestHeaders.set('x-request-agent', name)

  const res = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  })
  
  ...
import { ANDROID, ANDROID_CLIENT, IOS, IOS_CLIENT, UNKNOWN_CLIENT } from '@utils/constants'
import { headers } from 'next/headers'

type Props = {
  children: React.ReactNode
}

export const OSWrapper = ({ children }: Props) => {
  const heads = headers()
  const agent = heads.get('x-request-agent')

  let os

  switch (agent) {
    case ANDROID:
      os = ANDROID_CLIENT
      break
    case IOS:
      os = IOS_CLIENT
      break

    default:
      os = UNKNOWN_CLIENT
      break
  }

  return <div className={os}>{children}</div>
}

export default OSWrapper

That you I can use global css via:

* {
  :global(.android) {
    .wrapper {
      ...
    }
  }
}

Upvotes: 0

aravindanve
aravindanve

Reputation: 1069

Ideally it is best to do it the CSS way, as it is the most accurate. But if you need to make more complex layout decisions, you can use the user agent header to guess the type of device.

This is an example using React/NextJS/SSR. You can store the user-agent in a context and read it from a hook useIsMobile() which returns true or false based on the parsed user-agent header.

We create a context to store the parsed user-agent header.

// userAgentContext.ts
import { userAgent } from "next/server";
import { createContext } from "react";

export type UserAgent = ReturnType<typeof userAgent>;
export const UserAgentContext = createContext<UserAgent | undefined>(undefined);

We create a hook to consume the context and check device type.

// useIsMobile.ts
import { useContext } from "react";
import { UserAgentContext } from "./userAgentContext.ts";

export const useIsMobile = () => {
  const userAgent = useContext(UserAgentContext);
  if (!userAgent) {
    throw new Error("useIsMobile must be used within a UserAgentContext.Provider");
  }

  return userAgent.device.type === "mobile";
};

We can use a sever component as the layout (app router) to get the user agent from request headers

// layout.tsx
"use server";

import { headers } from "next/headers";
import { userAgent } from "next/server";
import { LayoutClient } from "./layoutClient.ts";

export default async function Layout({ children }: { children: React.ReactNode }) {
  const reqUserAgent = userAgent({ headers: headers() });
  return <LayoutClient reqUserAgent={reqUserAgent}>{children}</LayoutClient>
}

We can then pass the user agent to a client component that will provide the initial value for the context. Since client components with "use client" directive are still rendered once on the server with initial values then hydrated on the client.

// layoutClient.tsx
"use client";

import { UserAgentContext } from "./userAgentContext.ts";

export default function LayoutClient({ reqUserAgent, children }: { reqUserAgent: UserAgent, children: React.ReactNode }) {
  return <UserAgentContext.Provider value={reqUserAgent}>{children}</UserAgentContext.Provider>;
}

Now when this page is rendered for the first time on the server with initial values, useIsMobile() should accurately return a value based on the user-agent device type.

// page.tsx
"use client";

export default function Page() {
  const isMobile = useIsMobile();

  return isMobile ? <>Mobile</> : <>Desktop</>;
}

EDIT

You can rewrite the hook to return more precise selectors with react-device-detect

import { getSelectorsByUserAgent } from "react-device-detect";

export const useSelectorsByUserAgent = () => {
  const userAgent = useContext(UserAgentContext);
  if (!userAgent) {
    throw new Error("useUserAgent must be used within a UserAgentContext.Provider");
  }

  return getSelectorsByUserAgent(userAgent);
};
const { isIOS, isIPad13 } = useSelectorsByUserAgent();

return isIPad13 ? <>IPad 13</> : isIOS ? <>IOS</> : <>Other</>;

Upvotes: 1

Narcis
Narcis

Reputation: 173

One easy way to fix this is to use something like the NoSsr component from MUI.

For example

{ isMobile &&
   <NoSsr>
    <ComponentYouWantToRenderInMobile />
   </NoSsr>
}

That will stop the error from appearing.

So in OPs case:

import Link from "next/link";
import { BrowserView, MobileView } from "react-device-detect";
import { NoSsr } from "@mui/material";

export default () => (
  <div>
    Hello World.{" "}
    <Link href="/about">
      <a>About</a>
    </Link>
    <NoSsr>
      <BrowserView>
        <h1> This is rendered only in browser </h1>
      </BrowserView>
      <MobileView>
        <h1> This is rendered only on mobile </h1>
      </MobileView>
     </ NoSsr>
  </div>
);

Upvotes: 0

majid behzadnasab
majid behzadnasab

Reputation: 123

for ssr just use

import {headers} from 'next/headers';
const layout = ({children}: { children: React.ReactNode }) => {
    const headersList = headers()
    const isMobile = isMobileUserAgent(headersList.get('user-agent'))

and function is:

export const isMobileUserAgent = (userAgent) => {
    return /iPhone|iPad|iPod|Android/i.test(userAgent);
};

Upvotes: 3

Danmar Varela
Danmar Varela

Reputation: 153

react-device-detect now offers a getSelectorsByUserAgent you can use server-side and you need to pass the user-agent as an argument. If you are using app router in next.js, you can get the user-agent from the headers.

reference

// index.tsx
import { headers } from "next/headers"
import { getSelectorsByUserAgent } from "react-device-detect"
import MobileVersion from "./mobile"
import BrowserVersion from "./browser"

export default function SomeComponent() {
  // you can get more than just isMobile from this.
  const { isMobile } = getSelectorsByUserAgent(
    headers().get("user-agent") ?? ""
  )

  return isMobile ? <Mobile /> : <Browser />
}
//mobile.tsx
export default () => <div>this is the mobile version</div>
//browser.tsx
export default () => <div>this is the browser version</div>

Upvotes: 12

Abhishek Prajapati
Abhishek Prajapati

Reputation: 106

If you want to do something with user-agent information in nextjs from server side you'll have to use getServerSide props. because this is the only function that has access to req object. getStaticProps is not helpful.

First create a helper function just to reuse on several pages.

const getDevice = (userAgent) => {
let device = "";

if(userAgent && userAgent !== ""){
    let isMobile = userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i)
    if(isMobile && isMobile?.length > 0){
        device = "mobile";
    }
}

return device 
}

You can further modify above function as per your need.

Now in your getServerSideProps:

export const getServerSideProps = ({req}) => {
   const device = getDevice(req.headers['user-agent']);
   
   return {
      props: {
         device,
      }
   }
}

Now you have device information in your page. You can use to render different totally different layouts just like flipkart and olx.

NOTE : Changes will only reflect when a fresh page will be requested because server does not aware of client changes in viewport. If you want such thing probably you can use context api.

The downside is : You have to make each page that shifts layout, a server rendered page.

However if you are going to deploy your nextjs on netlify consider using middlewares with combination of @netlify/next package. More info here

Upvotes: 1

LOGAN
LOGAN

Reputation: 502

There is a way to resolve with react-device-detect.

    export async function getServerSideProps({ req, res }: GetServerSidePropsContext) {
    const userAgent = req.headers['user-agent'] || '';
    const { isMobile } = getSelectorsByUserAgent(userAgent);

    return {
        props: { isMobile },
    };
}

you can find more keys below because it is not specified on type definition of react-device-detect lib.

{
  isSmartTV: false,
  isConsole: false,
  isWearable: false,
  isEmbedded: false,
  isMobileSafari: false,
  isChromium: false,
  isMobile: false,
  isMobileOnly: false,
  isTablet: false,
  isBrowser: true,
  isDesktop: true,
  isAndroid: false,
  isWinPhone: false,
  isIOS: false,
  isChrome: true,
  isFirefox: false,
  isSafari: false,
  isOpera: false,
  isIE: false,
  osVersion: '10.15.7',
  osName: 'Mac OS',
  fullBrowserVersion: '107.0.0.0',
  browserVersion: '107',
  browserName: 'Chrome',
  mobileVendor: 'none',
  mobileModel: 'none',
  engineName: 'Blink',
  engineVersion: '107.0.0.0',
  getUA: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
  isEdge: false,
  isYandex: false,
  deviceType: 'browser',
  isIOS13: false,
  isIPad13: false,
  isIPhone13: false,
  isIPod13: false,
  isElectron: false,
  isEdgeChromium: false,
  isLegacyEdge: false,
  isWindows: false,
  isMacOs: true,
  isMIUI: false,
  isSamsungBrowser: false
}

Upvotes: 3

Simeon Borisov
Simeon Borisov

Reputation: 664

I solved a case like this using next-useragent.

const mobileBreakpoint = 1280;
/**
 * 
 * @param userAgent - the UserAgent object from `next-useragent`
 */
export const useIsMobile = (userAgent?: UserAgent): boolean => {
  const [isMobile, setIsMobile] = useState(false);
  // Some front-end hook that gets the current breakpoint, but returns undefined, if we don't have a window object.
  const { breakpoint } = useResponsive();
  useEffect(() => {
    if (breakpoint) {
      setIsMobile(breakpoint.start < mobileBreakpoint);
    }
    else if (userAgent) {
      setIsMobile(userAgent.isMobile);
    } else if (!isBrowser) {
      setIsMobile(false);
    }
  }, [userAgent, breakpoint]);
  return isMobile;
};

And the usage of it is:

// Inside react function component.
const isMobile = useIsMobile(props.userAgent);
export const getServerSideProps = (
  context: GetServerSidePropsContext,
): GetServerSidePropsResult<{ userAgent?: UserAgent }> => ({
  // Add the user agent to the props, so we can use it in the window hook.
  props: {
    userAgent: parse(context.req.headers["user-agent"] ?? ""),
  },
});

This hook always returns a boolean isMobile. When you run it server-side, it uses the user-agent header to detect a mobile device in the SSR request. When this gets to client side, it uses the breakpoints (in my case), or any other logic for width detection to update the boolean. You could use next-useragent to also detect the specific device type, but you can't make resolution-based rendering server-side.

Upvotes: 0

BinodNepali
BinodNepali

Reputation: 257

When I was working on one of my next.js projects, I came across a similar situation. I have got some ideas from the answers. And I did solve it with the following approach.

Firstly, I made custom hook using react-device-detect

//hooks/useDevice.ts
import { isDesktop, isMobile } from 'react-device-detect';

interface DeviceDetection {
  isMobile: boolean;
  isDesktop: boolean;
}

const useDevice = (): DeviceDetection => ({
  isMobile,
  isDesktop
});

export default useDevice;

Secondly, I made a component which uses of custom hook

//Device/Device.tsx
import { ReactElement } from 'react';

import useDevice from '@/hooks/useDevice';

export interface DeviceProps {
  desktop?: boolean;
  mobile?: boolean;
  children: ReactElement;
}

export const Device = ({ desktop, mobile, children }: DeviceProps): ReactElement | null => {
  const { isMobile } = useDevice();

  return (isMobile && mobile) || (!isMobile && desktop) ? children : null;
};

Thirdly, I import the component dynamically using next.js next/dynamic

//Device/index.tsx
import dynamic from 'next/dynamic';
    
import type { DeviceProps } from './Device';
    
export const Device = dynamic<DeviceProps>(() => import('./Device').then((mod) => mod.Device), {
      ssr: false
    });

Finally, I used it following way in pages.

//pages/my-page.tsx
import { Device } from '@/components/Device';
<Device desktop>
    <my-component>Desktop</my-component>
 </Device>
<Device mobile>
    <my-component>Mobile</my-component>
 </Device>

Upvotes: 2

IliasT
IliasT

Reputation: 4301

Was able to avoid dynamic importing or component props, by using React state instead. For my use case, I was trying to detect if it was Safari, but this can work for other ones as well.

Import code

import { browserName } from 'react-device-detect';

Component code

const [isSafari, setIsSafari] = useState(false);

useEffect(() => {
  setIsSafari(browserName === 'Safari');
}, [browserName]);

// Then respect the state in the render
return <div data-is-safari={isSafari} />;

Upvotes: 2

guest
guest

Reputation: 2234

This always works. (I used this package after trying the above technique and it didn't work for me.)

The advantage: The component renders server side so there's no flashing on client side when trying to detect user agent.

import { isMobile } from "mobile-device-detect";

just import the package and create your layout.

import { isMobile } from "mobile-device-detect";

const Desktop = () => {
  return (
    <>
      desktop
    </>
  );
};

Desktop.layout = Layout;

const Mobile = () => {
  return (
    <>
      mobile
    </>
  );
};

Mobile.layout = LayoutMobile;

const Page = isMobile ? Desktop : Mobile;

export default Page;

Upvotes: -1

Someone Special
Someone Special

Reputation: 13588

Load only the JS files needed dynamically

You can load components dynamically with next/dynamic, and only the appropriate component will be loaded.

You can use react-detect-device or is-mobile and in my case. In this scenario, I created separate layout for mobile and desktop, and load the appropriate component base on device.

import dynamic from 'next/dynamic';
const mobile = require('is-mobile');

const ShowMobile = dynamic(() => mobile() ? import('./ShowMobile.mobile') : import('./ShowMobile'), { ssr: false })


const TestPage = () => {

   return <ShowMobile />
}

export default TestPage

You can view the codesandbox . Only the required component.JS will be loaded.

Edit:

How different is the above from conditionally loading component? e.g.

isMobile ? <MobileComponent /> : <NonMobileComponent />

The first solution will not load the JS file, while in second solution, both JS files will be loaded. So you save one round trip.

Upvotes: 5

Saad Ahmed
Saad Ahmed

Reputation: 777

import React, { useState, useEffect }
import { isMobile } from 'react-device-detect'

...


const [_isMobile, setMobile] = useState();

    useEffect(() => {
        setMobile(isMobile);
    }, [setMobile]);

<div hidden={_isMobile}> Desktop View</div>

<div hidden={!_isMobile}> MobileView </div>

Upvotes: 1

Paul van Dyk
Paul van Dyk

Reputation: 1016

LATEST UPDATE:

So if you don't mind doing it client side you can use the dynamic importing as suggested by a few people below. This will be for use cases where you use static page generation.

i created a component which passes all the react-device-detect exports as props (it would be wise to filter out only the needed exports because then does not treeshake)

// Device/Device.tsx

import { ReactNode } from 'react'
import * as rdd from 'react-device-detect'

interface DeviceProps {
  children: (props: typeof rdd) => ReactNode
}
export default function Device(props: DeviceProps) {
  return <div className="device-layout-component">{props.children(rdd)}</div>
}

// Device/index.ts

import dynamic from 'next/dynamic'

const Device = dynamic(() => import('./Device'), { ssr: false })

export default Device

and then when you want to make use of the component you can just do

const Example = () => {
  return (
    <Device>
      {({ isMobile }) => {
        if (isMobile) return <div>My Mobile View</div>
        return <div>My Desktop View</div>
      }}
    </Device>
  )
}

Personally I just use a hook to do this, although the initial props method is better.

import { useEffect } from 'react'

const getMobileDetect = (userAgent: NavigatorID['userAgent']) => {
  const isAndroid = () => Boolean(userAgent.match(/Android/i))
  const isIos = () => Boolean(userAgent.match(/iPhone|iPad|iPod/i))
  const isOpera = () => Boolean(userAgent.match(/Opera Mini/i))
  const isWindows = () => Boolean(userAgent.match(/IEMobile/i))
  const isSSR = () => Boolean(userAgent.match(/SSR/i))
  const isMobile = () => Boolean(isAndroid() || isIos() || isOpera() || isWindows())
  const isDesktop = () => Boolean(!isMobile() && !isSSR())
  return {
    isMobile,
    isDesktop,
    isAndroid,
    isIos,
    isSSR,
  }
}
const useMobileDetect = () => {
  useEffect(() => {}, [])
  const userAgent = typeof navigator === 'undefined' ? 'SSR' : navigator.userAgent
  return getMobileDetect(userAgent)
}

export default useMobileDetect

I had the problem that scroll animation was annoying on mobile devices so I made a device based enabled scroll animation component;

import React, { ReactNode } from 'react'
import ScrollAnimation, { ScrollAnimationProps } from 'react-animate-on-scroll'
import useMobileDetect from 'src/utils/useMobileDetect'

interface DeviceScrollAnimation extends ScrollAnimationProps {
  device: 'mobile' | 'desktop'
  children: ReactNode
}

export default function DeviceScrollAnimation({ device, animateIn, animateOut, initiallyVisible, ...props }: DeviceScrollAnimation) {
  const currentDevice = useMobileDetect()

  const flag = device === 'mobile' ? currentDevice.isMobile() : device === 'desktop' ? currentDevice.isDesktop() : true

  return (
    <ScrollAnimation
      animateIn={flag ? animateIn : 'none'}
      animateOut={flag ? animateOut : 'none'}
      initiallyVisible={flag ? initiallyVisible : true}
      {...props}
    />
  )
}

UPDATE:

so after further going down the rabbit hole, the best solution i came up with is using the react-device-detect in a useEffect, if you further inspect the device detect you will notice that it exports const's that are set via the ua-parser-js lib

export const UA = new UAParser();

export const browser = UA.getBrowser();
export const cpu = UA.getCPU();
export const device = UA.getDevice();
export const engine = UA.getEngine();
export const os = UA.getOS();
export const ua = UA.getUA();
export const setUA = (uaStr) => UA.setUA(uaStr);

This results in the initial device being the server which causes false detection.

I forked the repo and created and added a ssr-selector which requires you to pass in a user-agent. which could be done using the initial props


UPDATE:

Because of Ipads not giving a correct or rather well enough defined user-agent, see this issue, I decided to create a hook to better detect the device

import { useEffect, useState } from 'react'

function isTouchDevice() {
  if (typeof window === 'undefined') return false
  const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ')
  function mq(query) {
    return typeof window !== 'undefined' && window.matchMedia(query).matches
  }
  // @ts-ignore
  if ('ontouchstart' in window || (window?.DocumentTouch && document instanceof DocumentTouch)) return true
  const query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('') // include the 'heartz' - https://git.io/vznFH
  return mq(query)
}

export default function useIsTouchDevice() {
  const [isTouch, setIsTouch] = useState(false)
  useEffect(() => {
    const { isAndroid, isIPad13, isIPhone13, isWinPhone, isMobileSafari, isTablet } = require('react-device-detect')
    setIsTouch(isTouch || isAndroid || isIPad13 || isIPhone13 || isWinPhone || isMobileSafari || isTablet || isTouchDevice())
  }, [])

  return isTouch

Because I require the package each time I call that hook, the UA info is updated, it also fixes to SSR out of sync warnings.

Upvotes: 51

Melounek
Melounek

Reputation: 900

If you don't mind rendering always desktop version and figuring the logic on the front-end, then the hook logic can be pretty straightforward.

export const useDevice = () => {
  const [firstLoad, setFirstLoad] = React.useState(true);
  React.useEffect(() => { setFirstLoad(false); }, []);

  const ssr = firstLoad || typeof navigator === "undefined";

  const isAndroid = !ssr && /android/i.test(navigator.userAgent);
  const isIos = !ssr && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

  return {
    isAndroid,
    isIos,
    isDesktop: !isAndroid && !isIos
  };
};

Upvotes: 0

BernardA
BernardA

Reputation: 1523

With current Next.js (v 9.5+) I accomplished that using next/dynamic and react-detect-device.

For instance, on my header component:

...
import dynamic from 'next/dynamic';
...

const MobileMenuHandler = dynamic(() => import('./mobileMenuHandler'), {
 ssr: false,
});

return (
...
    <MobileMenuHandler
        isMobileMenuOpen={isMobileMenuOpen}
        setIsMobileMenuOpen={setIsMobileMenuOpen}
    />
)
...

Then on MobileMenuHandler, which is only called on the client:

import { isMobile } from 'react-device-detect';
...
return(
   {isMobile && !isMobileMenuOpen ? (
       <Menu
          onClick={() => setIsMobileMenuOpen(true)}
          className={classes.menuIcon}
       />
   ) : null}
)

With that, the react-detect-device is only active on the client side and can give a proper reading.

See Next.js docs.

Upvotes: 4

Dylanbob211
Dylanbob211

Reputation: 1254

I think you should do it by using getInitialProps in your page, as it runs both on the server and on the client, and getting the device type by first detecting if you are just getting the request for the webpage (so you are still on the server), or if you are re-rendering (so you are on the client).

// index.js

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

Now you can use a regex to see if the device is a mobile or a desktop.

// still in getInitialProps

let isMobile = Boolean(userAgent.match(
  /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
))

return { isMobile }

Now you can access the isMobile prop that will return either true or false

const IndexPage = ({ isMobile }) => {
  return ( 
    <div>
     {isMobile ? (<h1>I am on mobile!</h1>) : (<h1>I am on desktop! </h1>)} 
    </div>
  )
}

I got this answer from this article here I hope that was helpful to you

UPDATE

Since Next 9.5.0, getInitialProps is going to be replaced by getStaticProps and getServerSideProps. While getStaticProps is for fetching static data, which will be used to create an html page at build time, getServerSideProps generates the page dynamically on each request, and receives the context object with the req prop just like getInitialProps. The difference is that getServerSideProps is not going to know navigator, because it is only server side. The usage is also a little bit different, as you have to export an async function, and not declare a method on the component. It would work this way:

const HomePage = ({ deviceType }) => {
let componentToRender
  if (deviceType === 'mobile') {
    componentToRender = <MobileComponent />
  } else {
    componentToRender = <DesktopComponent />
  }

  return componentToRender
}

export async function getServerSideProps(context) {
  const UA = context.req.headers['user-agent'];
  const isMobile = Boolean(UA.match(
    /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
  ))
  
  return {
    props: {
      deviceType: isMobile ? 'mobile' : 'desktop'
    }
  }
}


export default HomePage

Please note that since getServerSideProps and getStaticProps are mutually exclusive, you would need to give up the SSG advantages given by getStaticProps in order to know the device type of the user. I would suggest not to use getServerSideProps for this purpose if you need just to handle a couple of styiling details. If the structure of the page is much different depending on the device type than maybe it is worth it

Upvotes: 41

Related Questions