Skip to content

Instantly share code, notes, and snippets.

@SebastianUdden
Last active December 6, 2020 09:42
Show Gist options
  • Select an option

  • Save SebastianUdden/7c094bcb59702f014baff9ea44e646e2 to your computer and use it in GitHub Desktop.

Select an option

Save SebastianUdden/7c094bcb59702f014baff9ea44e646e2 to your computer and use it in GitHub Desktop.
Setup a progressive web app with gatsby and styled-components

Go to gists overview

Setup a progressive web app with gatsby and styled-components

TLDR; You want to build an awesome modern web application? I show you how!

Prerequisites

Check that you have an updated version of the below requirements by entering the following into the command-line interface (eg. iTerm2):

Xcode version

gcc --version

Node version

node -v

Gatsby CLI version

gatsby -v

Install any of the missing preequisites before continuing.

1. Setup the basic 'hello world' project

gatsby new repo_name https://github.com/gatsbyjs/gatsby-starter-hello-world

2. Install default dependencies

npm install

3. Install styled components and prefetch google fonts

npm install --save gatsby-plugin-styled-components styled-components babel-plugin-styled-components gatsby-plugin-prefetch-google-fonts gatsby-plugin-react-helmet react-helmet

Replace TITLE, DESCRIPTION and AUTHOR with your values and then add the following code in ./gatsby-config.js located in the root (next to package.json).

module.exports = {
 siteMetadata: {
    title: `TITLE`,
    description: `DESCRIPTION`,
    author: `@AUTHOR`,
  },
  plugins: [
    `gatsby-plugin-styled-components`,
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-plugin-prefetch-google-fonts`,
      options: {
        fonts: [
          {
            family: `Roboto Mono`,
            variants: [`400`, `700`]
          },
          {
            family: `Roboto`,
            subsets: [`latin`]
          }
        ]
      }
    }
  ]
};

4. Run the project

Test that the project works correctly by running.

gatsby develop

A blank page should appear at localhost:8000 with the words Hello World!

5. Add global styles

Add the following css to a new CSS-file at ./src/styles/global.css

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: "Roboto", "sans-serif";
}

Create a file called ./gatsby-browser.js with the following code in the root (next to package.json)

import "./src/styles/global.css";

6. Create Layout component

Add code for a layout component in a new JS-file at ./src/components/Layout.js.

import React from "react"
import styled from "styled-components"
import Header from "./Header"
import Footer from "./Footer"

const Wrapper = styled.div`
  min-height: 100vh;
  display: flex;
  flex-direction: column;
`
const Body = styled.div`
  flex: 1 0 auto;
  padding: 0 1rem;
`

export default ({ meta, categories, children }) => {
  return (
    <Wrapper>
      <Header meta={meta} categories={categories} />
      <Body>{children}</Body>
      <Footer />
    </Wrapper>
  )
}

7. Create Header component

Add code for a dynamic fixed header component that appears when scrolling up and dissapears when scrolling down in a new JS-file at ./src/components/Header.js

import React, { useEffect, useState } from "react"
import styled from "styled-components"
import SEO from "./seo"
import Link from "./Link"

const Wrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1rem;
  background-color: #ccc;
  top: 0;
  position: sticky;
  transform: ${p => (p.show ? "translateY(0%)" : "translateY(-100%)")};
  transition: transform 0.4s;
`
const List = styled.ul``

const ListItem = styled(Link)`
  margin-left: 1rem;
`
const HomeLink = styled(Link)`
  font-size: 24px;
`
const ListLink = ({ to, children }) => <ListItem to={to}>{children}</ListItem>

export default ({ meta, categories = [] }) => {
  let [position, setPosition] = useState(0)
  let [visible, setVisible] = useState(true)

  useEffect(() => {
    const handleScroll = () => {
      let tempPosition = window.pageYOffset

      if (position > 100) {
        setVisible(position > tempPosition)
      } else {
        setVisible(true)
      }
      setPosition(tempPosition)
    }
    window.addEventListener("scroll", handleScroll)
    return () => {
      window.removeEventListener("scroll", handleScroll)
    }
  })
  return (
    <Wrapper show={visible}>
      <SEO {...meta} />
      <HomeLink to="/">Home</HomeLink>
      <List>
        {categories.map(category => (
          <ListLink to={category.slug}>{category.title}</ListLink>
        ))}
      </List>
    </Wrapper>
  )
}

8. Create Footer component

Add code for a fixed bottom footer component with basic styling in a new JS-file at ./src/components/Footer.js.

import React from "react";
import styled from "styled-components";

const Wrapper = styled.div`
  flex-shrink: 0;
  background-color: #ccc;
  padding: 1rem;
`;

export default () => <Wrapper>Footer</Wrapper>;

9. Create Link component

Add code for a link component with basic styling in a new JS-file at ./src/components/Link.js.

import { Link as LinkUI } from "gatsby";
import styled from "styled-components";

const Link = styled(LinkUI)`
  text-decoration: none;
  color: black;
  cursor: pointer;
`;

export default Link;

10. Create Seo component

Add code for an Seo component in a new JS-file at ./src/components/Seo.js

/**
 * SEO component that queries for data with
 *  Gatsby's useStaticQuery React hook
 *
 * See: https://www.gatsbyjs.org/docs/use-static-query/
 */

import React from "react"
import Helmet from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"

const SEO = ({ description, lang = "", meta = [], title, url }) => {
  const { site } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
            description
            author
          }
        }
      }
    `
  )

  const metaTitle = title || site.siteMetadata.title
  const metaDescription = description || site.siteMetadata.description

  return (
    <Helmet
      htmlAttributes={{
        lang,
      }}
      title={metaTitle}
      titleTemplate={`%s | ${site.siteMetadata.title}`}
      meta={[
        {
          name: `description`,
          content: metaDescription,
        },
        {
          property: `og:title`,
          content: metaTitle,
        },
        {
          property: `og:description`,
          content: metaDescription,
        },
        {
          property: `og:type`,
          content: `website`,
        },
        {
          name: `twitter:card`,
          content: `summary`,
        },
        {
          name: `twitter:creator`,
          content: site.siteMetadata.author,
        },
        {
          name: `twitter:title`,
          content: metaTitle,
        },
        {
          name: `twitter:description`,
          content: metaDescription,
        },
      ].concat(meta)}
    >
      <link rel="canonical" href={url} />
    </Helmet>
  )
}

export default SEO

11. Create data for category pages

Add the following data into a new JSON-file at ./src/data/categories.json

[
  {
    "slug": "/him/",
    "title": "Him"
  },
  {
    "slug": "/her/",
    "title": "Her"
  }
]

12. Create template page for home

Add template code for a home page in a new JS-file at ./src/templates/homePage.js.

import React from "react"
import Layout from "../components/Layout"

export default ({ pageContext: { categories } }) => {
  return (
    <Layout meta={{ title: "Home" }} categories={categories}>
      <h1>Home</h1>
    </Layout>
  )
}

13. Create template page for category

Add template code for category page in a new JS-file at ./src/templates/categoryPage.js.

import React from "react"
import Layout from "../components/Layout"

export default ({
  pageContext: {
    categories,
    page: { title },
  },
}) => {
  return (
    <Layout meta={{ title }} categories={categories}>
      <h1>{title}</h1>
    </Layout>
  )
}

14. Create pages dynamically based on the data

Add a new file called gatsby-node.js with the following code in the root (next to package.json)

const categoriesRaw = require("./src/data/categories.json");

exports.createPages = async ({ actions: { createPage } }) => {
  const categories = Object.values(categoriesRaw);
  createPage({
    path: `/`,
    component: require.resolve("./src/templates/homePage.js"),
    context: { categories }
  });

  categories.forEach(page => {
    createPage({
      path: page.slug,
      component: require.resolve("./src/templates/categoryPage.js"),
      context: { categories, page }
    });
  });
};

15. Remove old pages folder

Remove the old pages folder at ./src/pages

16. Install dependencies for the Web App Manifest, Service Worker

npm install --save gatsby-plugin-manifest gatsby-plugin-offline

17. Add an icon image

You can use a custom logo or the default Gatsby logo by downloading it from https://www.gatsbyjs.org/Gatsby-Monogram.svg. Then save it to ./src/images/icon.svg

18. Add plugin and manifest settings

Replace REPOSITORY_NAME and REPO_NAME with names of your choice then add the following.

// in gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `REPOSITORY_NAME`,
        short_name: `REPO_NAME`,
        start_url: `/`,
        background_color: `#f7f0eb`,
        theme_color: `#222222`,
        display: `standalone`,
        icon: `src/images/icon.svg`,
      },
    },
    `gatsby-plugin-offline`,
  ],
}

19. Add service worker functionality

// in gatsby-browser.js
export const onServiceWorkerUpdateReady = () => {
  const answer = window.confirm(
    `This application has been updated. ` +
      `Reload to display the latest version?`
  )
  if (answer === true) {
    window.location.reload()
  }
}

20. Run the project

Build and serve the project to see all the parts in action:

  • A progressive web app
  • Dynamically created pages
  • A layout structure with header, body and footer.
gatsby build && gatsby serve

21. Test the app with lighthouse

  1. Open the chrome browser
  2. Access dev-tools with cmd+shift+i
  3. Go to the audits tab and click generate report

22. See the impressive lighthouse score provided by this template

  • Performance 92
  • Accessibility 94
  • Best practices 93
  • SEO 100
  • PWA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment