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 useWindowSize
or useIsMobile
weren’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
- Creating React Context for convenient usage
- Final implementation of
useIsMobile
hook - How to use it on the page
- Resources
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;
};
useIsMobile
detects 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
- Mobile Detect library
- GetServerSideProps documentation
- React Context documentation
- UseWindowsSize hook implementation