skip to Main Content

📖 Summary

Recently my team started a project of a landing page and we chose to use Gatsby in order to have good SEO.
At a point in our project, the designers changed the mobile layouts to be a SPA, and the desktop ones still having different routes and pages.

Refer to that example:

enter image description here
enter image description here

Since Gatsby creates pages in build time, we don’t know if the environment is mobile or desktop, it’s difficult to think in a way to deal with that behavior.


🐛 Workaround

One quick way that our team thought to temporarily resolve that problem was to map between sections and hide than in desktop screens.
And the biggest problem is: On the first load of the page the content takes almost a second to load because it’s not static anymore.

      <div>
        {
          breakpoints.md
            ? pages.map((page) => renderPage(page))
            : renderPage(selectedPageRef.current)
        }
      </div>

🚀 Goals

I would like to discuss about a solution that will change the behavior of the pages in desktop and mobile without killing the SEO of the application.

2

Answers


  1. If you can’t solve by using mediaqueries and you must display two different components rather than the same styled. The workaround to solve this is to check what is the window size at the rendered time and show one layout or another. It would create a minimum delay (insignificant if cached) before the header is shown but this is the only way I am able to guess.

    So, using Gatsby’s default structure in <Layout> component you should have something like that:

    return (
        <>
          <Header siteTitle={data.site.siteMetadata.title} />
          <div>
            <main>{children}</main>
            <footer>
              © {new Date().getFullYear()}, Built with
              {` `}
              <a href="https://www.gatsbyjs.org">Gatsby</a>
            </footer>
          </div>
        </>
      )
    

    So, in your <Header> component you should check your window size and render one component or another:

    export const Header = (props) => {
      let currentWidth;
    
      if (typeof window !== 'undefined') currentWidth = useWindowWidth();
    
      return typeof window !== 'undefined' ? currentWidth >= 768 ? <DesktopPane /> : <MobilePane /> : null;
    };
    

    As you can see, in the return I check if window is defined in a ternary chained condition. If the window is not defined (i.e: is undefined) it returns a null. If it’s defined, it checks the current window’s width (currentWidth) and there’s another ternary condition that displays the mobile or the desktop menu.

    As a best practice, chained ternaries are not the cleanest solution, they are difficult to read and maintain but for now, the solution works (and of course it must be refactored).

    In this case, useWindowWidth() is a custom hook that calculates in every window the size but you can use whatever you like. It looks like:

    import {useEffect, useState} from 'react';
    
    const getWidth = () => window.innerWidth
      || document.documentElement.clientWidth
      || document.body.clientWidth;
    
    export const useWindowWidth = () => {
      let [width, setWidth] = useState(getWidth());
    
      useEffect(() => {
        let timeoutId = null;
        const resizeListener = () => {
          clearTimeout(timeoutId);
          timeoutId = setTimeout(() => setWidth(getWidth()), 150);
        };
        window.addEventListener('resize', resizeListener);
        return () => {
          window.removeEventListener('resize', resizeListener);
        };
      }, []);
    
      return width;
    };
    

    Code provided by: https://usehooks.com/useWindowSize/

    Note that it’s common in Gatsby’s projects to check if the window is !== than undefined due to the point you argued. At the compilation/build point isn’t defined yet. You can check for further information about Debugging HTML Builds in their documentation.

    Login or Signup to reply.
  2. First, you can achieve the following with the help of CSS media query (hide the sidebar or transform it to be the top nav):
    enter image description here

    Then, you can set up a useMediaQuery hook (for example here is an implementation) to conditionally render the About and Events components in one of the two ways shown below:

    A) If most users are using desktop, you can defer the import of the other two components (About and Events) in mobile view with loadable-components:

    The Index page will look something like this:

    import React from "react"
    import Loadable from "@loadable/component"
    import useMediaQuery from "use-media-query-hook"
    import Sidebar from "../components/Sidebar"
    import Home from "../components/Home"
    
    const LoadableAbout = Loadable(() => import("../components/About"))
    const LoadableEvents = Loadable(() => import("../components/Events"))-
    
    const IndexPage = () => {
      const isMobile = useMediaQuery("(max-width: 425px)")
      return (
        <div>
          <Sidebar />
          <Home />
          {isMobile && <LoadableAbout />}
          {isMobile && <LoadableEvents />}
        </div>
      )
    }
    
    export default IndexPage
    

    B) If most users are using mobile, you can include the two components in the main bundle at build time:

    The Index page will look something like this:

    import React from "react"
    import useMediaQuery from "use-media-query-hook"
    import Sidebar from "../components/Sidebar"
    import Home from "../components/Home"
    import About from "../components/About"
    import Events from "../components/Events"
    
    const IndexPage = () => {
      const isMobile = useMediaQuery("(max-width: 425px)")
      return (
        <div>
          <Sidebar />
          <Home />
          {isMobile && <About />}
          {isMobile && <Events />}
        </div>
      )
    }
    
    export default IndexPage
    

    Regarding SEO, the search engine will only see the main component (Home in route “/”, About in “/about”, etc) since isMobile defaults to null at build time in the above implementations.

    Regarding speed, the main components are statically rendered in the HTML. Only in mobile SPA view, other sections are needed to be loaded.

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