skip to Main Content

I’m trying to serve a static page in Next.js with responsive design for different device sizes. I am using both CSS media queries as well as JS responsive tools so it should have different layout for different screen sizes.

For a fully static site, it doesn’t seem possible to serve different static builds with different layouts for different screen sizes as the server has no knowledge of the client’ screen size.

I can partially build some info on the server and build the rest on the client but since my layout is completely different on small and large screens, so I’ll have to build the whole layout in the client.

Here’s some code I’ve tried:

import dynamic from 'next/dynamic';
import Header from '../components/Header';
import Footer from '../components/Footer';

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

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

export default function Home({ data }) {
  return (
    <div>
      { isMobile ? 
        <MobileLayout data={ data } /> : 
        <DesktopLayout data={ data } /> }

    </div>
  );
}

export async function getServerSideProps() {
  const data = await fetchData();
  return { props: { data } };
}

export default HomePage;

This way, I get hydration error as the isMobile function can’t run on the server and since the whole layout is not served statically pre-rendered but rendered on the client, I will miss the benefits of SSG and SEO.

Is there a way to serve different static builds for different screen sizes?

Any help would be greatly appreciated. Thanks in advance!

2

Answers


  1. I’ve been find the best practice for this, and I’m not sure but you can try my thinking:

    1. Serve 2 apps (let say mobile version on port 3001 and desktop version on port 3002). Use nginx to detect user agent from request, then point to mobile/desktop respectively.
      -> This approach require double cost on server, and double logic/business layer. If you use monorepo, let’s split source base into libs, utils, ui components,… & reuse as much code as possible, I think it’s worth.
      And try not to implement 2 very different logic between 2 versions.

    2. I’m trying using middleware to detect mobile or desktop device and rewrite the destination to pages/mobile/ or pages/desktop/
      -> This approach will not double server cost but you have to manage the Layout in custom app (pages/_app).

    Login or Signup to reply.
  2. The best approach is to let css do as much of the work as possible. You can show / hide entire sections of markup based on media queries. For example, if you’re using tailwind,

    
    <div>
      <MobileHeader className='md:hidden' />
      <DesktopHeader className='hidden md:visible' />
    </div>
    

    This is both performant (the css engine is highly optimized), and relatively straightforward.

    On some rare occasions you may need to know a page’s layout during SSR based on some condition. The best way to do that (if you truly need it!), is to query the userAgent string using next’s middleware mechanism. For example,

    // src/middleware.ts
    
    import { NextRequest, NextResponse, userAgent } from 'next/server'
    import { getSelectorsByUserAgent } from 'react-device-detect'
    
    export const middleware = async (request: NextRequest) => {
    
      const ua = userAgent(request)
      const { isMobileOnly, isTablet, isDesktop } = getSelectorsByUserAgent(ua.ua)
      const agent = isMobileOnly ? 'phone' : (isTablet ? 'tablet' : (isDesktop ?  'desktop' : 'unknown'))
      const { nextUrl: url } = request
      url.searchParams.set('agent', agent)
      return NextResponse.rewrite(url)
    }
    

    and then in your layout…

    // src/app/page.tsx
    type PageProps = {
      params: { slug: string };
      searchParams?: { [key: string]: string | string[] | undefined };
    }
    
    const Page: React.FC<PageProps> = ({
      searchParams
    }) => {
        // see src/middleware.ts
      const agent = searchParams?.agent
    
      if (agent === 'desktop') {
        return <Desktop />
      }
      else if (agent === 'tablet') {
        return <TouchDevice isTablet={true} />
      }
      return <TouchDevice isTablet={false} />
    }
    
    export default Page
    

    I needed to do this to implement scroll snap behavior optimally on pointer and touch devices.

    Con 1: You are limit to what the useragent string actually provides, which doesn’t include the actual resolution. Taking a look at the source code for the module I used above, react-device-detect, will give you a comprehensive picture of what is and isn’t possible.

    Con 2: You’re using searchParams in a slightly unusual way which can be brittle.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search