Skip to content

Instantly share code, notes, and snippets.

@mjbalcueva
Last active July 20, 2024 12:47
Show Gist options
  • Save mjbalcueva/a875538b9dac92a3e660e031652bb3dc to your computer and use it in GitHub Desktop.
Save mjbalcueva/a875538b9dac92a3e660e031652bb3dc to your computer and use it in GitHub Desktop.
Server-Side Device-Based Conditional Rendering in Next.js

Server-Side Device-Based Conditional Rendering in Next.js

Problem

You want to conditionally render components on the server side based on the device type (mobile, tablet, desktop). The goal is to avoid rendering all components server-side and hiding them with CSS, and to prevent client-side JavaScript from causing a flash of content as it loads.

Solution

Create a hook that determines the device type based on window size and stores it in a cookie. Use this context to conditionally render components according to the device type.

Implementation

1. Create a new file called useDeviceSize.ts

'use client'

import * as React from 'react'

type DeviceType = 'mobile' | 'tablet' | 'desktop' | undefined

const useDeviceSize = ({ defaultDeviceSize }: { defaultDeviceSize: DeviceType }) => {
	const [deviceSize, setDeviceSize] = React.useState<DeviceType>(defaultDeviceSize)

	const getDeviceSize = (): DeviceType => {
		if (window.matchMedia('(min-width: 1024px)').matches) return 'desktop'
		if (window.matchMedia('(min-width: 768px)').matches) return 'tablet'
		if (window.matchMedia('(min-width: 0px)').matches) return 'mobile'
	}

	React.useEffect(() => {
		const handleResize = () => {
			const currentDeviceSize = getDeviceSize()
			setDeviceSize(currentDeviceSize)
			document.cookie = `device-size=${JSON.stringify(currentDeviceSize)}`
		}

		handleResize()
		window.addEventListener('resize', handleResize)

		return () => window.removeEventListener('resize', handleResize)
	}, [])

	return { deviceSize }
}

export { useDeviceSize, type DeviceType }

2. Set the default device size in the layout component

import { cookies } from 'next/headers'

import { DynamicNav } from '@/components/dynamic-nav'

import { type DeviceType } from '@/lib/hooks/useDeviceSize'

export default function SiteLayout({ children }: Readonly<{ children: React.ReactNode }>) {
	const deviceSizeCookie = cookies().get('device-size')

	let defaultDeviceSize: DeviceType
	if (deviceSizeCookie) defaultDeviceSize = JSON.parse(deviceSizeCookie.value) as DeviceType

	return (
		<>
			{/* Sample Usage */}
			<DynamicNav defaultDeviceSize={defaultDeviceSize}>{children}</DynamicNav>
		</>
	)
}

3. Conditionally render components based on the device type

'use client'

import * as React from 'react'

import { useDeviceSize, type DeviceType } from '@/lib/hooks/useDeviceSize'

const DynamicNav: React.FC<{
	children: React.ReactNode
	defaultDeviceSize: DeviceType
}> = ({ children, defaultDeviceSize }) => {
	const { deviceSize } = useDeviceSize({ defaultDeviceSize })

	if (!deviceSize) {
		return <h1>loading...</h1>
	}

	if (deviceSize === 'mobile') {
		return (
			<>
				<h1>mobile</h1>
				{children}
			</>
		)
	}

	if (deviceSize === 'tablet') {
		return (
			<>
				<h1>Tablet</h1>
				{children}
			</>
		)
	}

	return (
		<div>
			<h1>Hello World</h1>
			{children}
		</div>
	)
}

export { DynamicNav }
@mjbalcueva
Copy link
Author

If you guys want to use tailwind sizes. modify the device-type-provider.tsx as follows:

type DeviceType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | undefined

const getDeviceSize = (): DeviceType => {
	if (window.matchMedia('(min-width: 1536px)').matches) return '2xl'
	if (window.matchMedia('(min-width: 1280px)').matches) return 'xl'
	if (window.matchMedia('(min-width: 1024px)').matches) return 'lg'
	if (window.matchMedia('(min-width: 768px)').matches) return 'md'
	if (window.matchMedia('(min-width: 640px)').matches) return 'sm'
	if (window.matchMedia('(min-width: 0px)').matches) return 'xs'
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment