The Right Way to Detect Mobile Breakpoints in Next.js

How to make useIsMobile hook in Next.js

Recently I was given a task to build an e-commerce app from scratch using NextJS. The app design was in two variants: desktop and mobile. And when I came to the mobile version, I was kind of frustrated. All my favorite hooks like useWindowSizeor useIsMobileweren’t working properly because of the server-side environment! They are dependent on the window global object, and it simply doesn’t exist on the server. So it is impossible to get the right initial window size, and also it’s not right to set null by default. It will probably cause layout shifts in the UI. So I had to find a way, to set a proper default value for the initial markup

I started to seek a solution and I found it. It is possible to detect mobile devices not by window width, but by their user agents. In this article, we are going to comprehend that easy and elegant solution.

Table of contents

Detecting mobile devices in getServerSideProps

getServerSideProps invokes on every page request. It gets context as the only argument and returns data required for the page. That’s the exact place to define the user device.

First of all, we need to write our core function, which will accept server-side context and detect mobile devices. As it is quite hard and unnecessary to write our custom implementation, we will use a tiny library called mobile-detect . So, let’s see the code!

import MobileDetect from "mobile-detect";
import { GetServerSidePropsContext } from "next";

export const getIsSsrMobile = (context: GetServerSidePropsContext) => {
  const md = new MobileDetect(context.req.headers["user-agent"] as string);

  return Boolean(md.mobile());
};

The core logic is done, but how to conveniently use it in code? We don’t want to pass the props all the way down, because it can quickly become confusing. It’s the right time for React Context !)

Creating React Context for convenient usage

Honestly, there is nothing complicated about it. We need just to create Context which will hold a boolean value.

import { createContext } from "react";

export const IsSsrMobileContext = createContext(false);

Please, don’t forget to wrap the app with it. The most suitable place for it is _app.tsx.

import type { AppProps } from "next/app";
import { IsSsrMobileContext } from "@/utils/useIsMobile";

export default function App({
  Component,
  pageProps,
}: AppProps<{ isSsrMobile: boolean }>) {
  return (
    <IsSsrMobileContext.Provider value={pageProps.isSsrMobile}>
      <Component {...pageProps} />
    </IsSsrMobileContext.Provider>
  );
}

Final implementation of useIsMobile hook

I believe we should start with the backbone of the hook and then add more complicated logic on top of it.

export const useIsMobile = () => {
  const isSsrMobile = useContext(IsSsrMobileContext);

  return isSsrMobile;
};

useIsMobiledetects mobile devices only on the server now, but what about browsers? Let’s write another hook, but now for the client side. It can look like that but feel free to write a solution suitable to your needs.

export const useWindowSize = () => {
  const [windowSize, setWindowSize] = useState<{
    width?: number;
    height?: number;
  }>({
    width: undefined,
    height: undefined
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }

    window.addEventListener("resize", handleResize);

    // Call handler right away so state gets updated with initial window size
    handleResize();

    // Don't forget to remove event listener on cleanup
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return windowSize;
};

And finally, we combine it all together…

export const useIsMobile = () => {
  const isSsrMobile = useContext(IsSsrMobileContext);
  const { width: windowWidth } = useWindowSize();
  const isBrowserMobile = !!windowWidth && windowWidth < 992;

  return isSsrMobile || isBrowserMobile;
};

How to use it on the page

import { getIsSsrMobile } from "@/utils/getIsSsrMobile";
import { useIsMobile } from "@/utils/useIsMobile";
import { GetServerSidePropsContext } from "next";

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

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

export async function getServerSideProps(context: GetServerSidePropsContext) {
  return {
    props: {
      isSsrMobile: getIsSsrMobile(context),
    },
  };
}

Please, remember to always return isSsrMobile property from getServerSideProps. The rest is quite straightforward.

Thanks for reading!

I hope you found this article useful.

Resources

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics