DEV Community

Cover image for Help serving assets over HTTP/2 for a Gatsby Netlify hosted site

Help serving assets over HTTP/2 for a Gatsby Netlify hosted site

Nick Taylor on January 29, 2019

Hi all. I threw out this Tweet into the Twitterverse, but thought it would be wise to ask for help here as well. // Detect dark theme var i...
Collapse
 
sebastienlorber profile image
Sebastien Lorber

Hey, signed up just to tell that http2 was not working on my Netlify site.

Turns out it was due to a firewall (Avast -> Web Shield -> Turn off "inspect https requests")

If sites like http2.pro/ tells you http2 works on your site, but both Chrome and FF show https1.1 protocol, I suggest you inspect your site from another computer/network and turn off your computer protections...

This is a really annoying thing if ubiquitous user software like Avast start to prevent website optimizations we work hard to setup...

Collapse
 
nickytonline profile image
Nick Taylor

Thanks for the heads up Sebastien!

Collapse
 
nickytonline profile image
Nick Taylor • Edited

So I'm actually on the wrong track. What I mention in the post is to enable HTTP2 Server Push which is an optional thing. The pages should be serving over HTTP2. @easyaspython put me on to the right track, but still not solved.

Collapse
 
darcyrayner profile image
Darcy Rayner

It should use HTTP 2 by default. It looks like all the requests that are HTTP 1.1 from the iamdeveloper domain are being loaded by a service worker. I reckon that has something to do with it.

Collapse
 
nickytonline profile image
Nick Taylor

Yeah that was what @easyaspython had guessed. So I guess a false reading from lighthouse?

Collapse
 
nickytonline profile image
Nick Taylor

It's apparently a bug with Chrome and Lighthouse.

ServiceWorker serving cache in HTTP/1.1 protocol #11123

rayriffy avatar
rayriffy commented on Jan 17, 2019

Description

I deployed my prodution and staging site on Netlify and I got reports from Lighthouse audits that some my resources are being served in HTTP/1.1 protocol which it shouldn't do that.

I found out later that all of the resurces that being served in HTTP/1.1 are from ServiceWorker. I think there's some issue with gatsby-plugin-offine

Steps to reproduce

  1. Open DevTools with Network tab
  2. Go to blog-staging.rayriffy.com. From this point you can see all resources are downloaded in HTTP/2 protocol
  3. Refresh page. And you should see cached data from ServiceWorker are served in HTTP/1.1

Expected result

ServiceWorker should serve cache in HTTP/2 protocol

Actual result

It served in HTTP/1.1 protocol

Environment

  System:
    OS: Windows 10
    CPU: x64 Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz
  Binaries:
    Yarn: 1.13.0 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
    npm: 6.5.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: 44.17763.1.0
  npmPackages:
    gatsby: ^2.0.91 => 2.0.91
    gatsby-image: ^2.0.26 => 2.0.26
    gatsby-paginate: ^1.0.16 => 1.0.16
    gatsby-plugin-feed: ^2.0.8 => 2.0.11
    gatsby-plugin-google-analytics: ^2.0.9 => 2.0.9
    gatsby-plugin-google-fonts: ^0.0.4 => 0.0.4
    gatsby-plugin-manifest: ^2.0.13 => 2.0.13
    gatsby-plugin-netlify: ^2.0.5 => 2.0.6
    gatsby-plugin-netlify-cache: ^1.0.0 => 1.0.0
    gatsby-plugin-offline: ^2.0.21 => 2.0.21
    gatsby-plugin-react-helmet: ^3.0.5 => 3.0.5
    gatsby-plugin-robots-txt: ^1.3.0 => 1.3.0
    gatsby-plugin-sharp: ^2.0.17 => 2.0.17
    gatsby-plugin-sitemap: ^2.0.4 => 2.0.4
    gatsby-plugin-typography: ^2.2.5 => 2.2.5
    gatsby-remark-copy-linked-files: ^2.0.8 => 2.0.8
    gatsby-remark-embed-gist: ^1.1.5 => 1.1.5
    gatsby-remark-embed-spotify: ^2.0.2 => 2.0.2
    gatsby-remark-images: ^3.0.1 => 3.0.1
    gatsby-remark-prismjs: ^3.2.0 => 3.2.0
    gatsby-remark-responsive-iframe: ^2.0.8 => 2.0.8
    gatsby-remark-smartypants: ^2.0.5 => 2.0.7
    gatsby-source-filesystem: ^2.0.16 => 2.0.16
    gatsby-transformer-json: ^2.1.7 => 2.1.7
    gatsby-transformer-remark: ^2.2.0 => 2.2.0
    gatsby-transformer-sharp: ^2.1.10 => 2.1.10

error The system cannot find the path specified.

gatsby-config.js

Source
var hostname

if (process.env.GATSBY_ENV === 'production') {
  hostname = 'https://blog.rayriffy.com'
} else if (process.env.GATSBY_ENV === 'staging') {
  hostname = 'https://blog-staging.rayriffy.com'
} else if (process.env.GATSBY_ENV === 'development') {
  hostname = 'https://localhost:8000'
}

module.exports = {
  siteMetadata: {
    title: 'Riffy Blog',
    author: 'Phumrapee Limpianchop',
    description: 'The Nerdy Blogger',
    siteUrl: `${hostname}`,
  },
  pathPrefix: '/',
  plugins: [
    {
      resolve: `gatsby-plugin-google-fonts`,
      options: {
        fonts: [`kanit`],
      },
    },
    `gatsby-plugin-netlify-cache`,
    `gatsby-transformer-json`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `./src/assets/database`,
      },
    },
    {
      resolve: 'gatsby-plugin-robots-txt',
      options: {
        resolveEnv: () => process.env.GATSBY_ENV,
        env: {
          production: {
            policy: [
              {
                userAgent: '*',
                disallow: ['/pages', '/category', '/author'],
              },
            ],
          },
          staging: {
            policy: [
              {
                userAgent: '*',
                disallow: ['/'],
              },
            ],
          },
          development: {
            policy: [
              {
                userAgent: '*',
                disallow: ['/'],
              },
            ],
          },
        },
      },
    },
    {
      resolve: `gatsby-plugin-netlify`,
      options: {
        headers: {
          '/feed.json': ['Access-Control-Allow-Origin: *'],
        },
      },
    },
    {
      resolve: `gatsby-plugin-sitemap`,
      options: {
        output: `/sitemap.xml`,
        exclude: [
          '/pages/*',
          '/category',
          '/category/*',
          '/author',
          '/author/*',
        ],
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages`,
        name: 'pages',
        ignore: [`**/.*`],
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/assets`,
        name: 'assets',
        ignore: [`**/.*`],
      },
    },
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          'gatsby-remark-embed-spotify',
          'riffy-gjs-embeded-video',
          {
            resolve: 'gatsby-remark-embed-gist',
            options: {
              username: 'rayriffy',
              includeDefaultCss: true,
            },
          },
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 1000,
              linkImagesToOriginal: false,
              sizeByPixelDensity: true,
              withWebp: true,
              quality: 80,
            },
          },
          'gatsby-remark-responsive-iframe',
          'gatsby-remark-prismjs',
          'gatsby-remark-copy-linked-files',
          'gatsby-remark-smartypants',
        ],
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: `${
          process.env.GATSBY_ENV === 'production'
            ? 'UA-85367836-2'
            : process.env.GATSBY_ENV === 'staging'
            ? 'UA-85367836-3'
            : ''
        }`,
      },
    },
    `gatsby-plugin-feed`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Riffy Blog`,
        short_name: `Riffy Blog`,
        start_url: `/`,
        background_color: `#f5f5f5`,
        theme_color: `#1e88e5`,
        display: `minimal-ui`,
        icon: `src/assets/logo.png`,
      },
    },
    {
      resolve: `gatsby-plugin-offline`,
      options: {
        dontCacheBustUrlsMatching: /(\.js$|\.css$|\/static\/)/,
        runtimeCaching: [
          {
            urlPattern: /(\.js$|\.css$|\/static\/)/,
            handler: `cacheFirst`,
          },
          {
            urlPattern: /^https?:\/\/(www\.blog.rayriffy\.com|localhost:8000|localhost:9000|blog-staging\.rayriffy\.com|blog\.rayriffy\.com).*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/,
            handler: `staleWhileRevalidate`,
          },
          {
            urlPattern: /^https?:\/\/fonts\.googleapis\.com\/css/,
            handler: `staleWhileRevalidate`,
          },
        ],
      },
    },
    `gatsby-plugin-react-helmet`,
    {
      resolve: 'gatsby-plugin-typography',
      options: {
        pathToConfigModule: 'src/utils/typography',
        omitGoogleFont: true,
      },
    },
  ],
}

package.json

Source
{
  "name": "rayriffy-blog",
  "description": "The Nerdy Blogger",
  "version": "1.0.10",
  "author": "Phumrapee Limpianchop <contact@rayriffy.com>",
  "bugs": {
    "url": "https://github.com/rayriffy/rayriffy-blog/issues"
  },
  "dependencies": {
    "cross-env": "^5.2.0",
    "gatsby": "^2.0.91",
    "gatsby-image": "^2.0.26",
    "gatsby-paginate": "^1.0.16",
    "gatsby-plugin-feed": "^2.0.8",
    "gatsby-plugin-google-analytics": "^2.0.9",
    "gatsby-plugin-google-fonts": "^0.0.4",
    "gatsby-plugin-manifest": "^2.0.13",
    "gatsby-plugin-netlify": "^2.0.5",
    "gatsby-plugin-netlify-cache": "^1.0.0",
    "gatsby-plugin-offline": "^2.0.21",
    "gatsby-plugin-react-helmet": "^3.0.5",
    "gatsby-plugin-robots-txt": "^1.3.0",
    "gatsby-plugin-sharp": "^2.0.17",
    "gatsby-plugin-sitemap": "^2.0.4",
    "gatsby-plugin-typography": "^2.2.5",
    "gatsby-remark-copy-linked-files": "^2.0.8",
    "gatsby-remark-embed-gist": "^1.1.5",
    "gatsby-remark-embed-spotify": "^2.0.2",
    "gatsby-remark-images": "^3.0.1",
    "gatsby-remark-prismjs": "^3.2.0",
    "gatsby-remark-responsive-iframe": "^2.0.8",
    "gatsby-remark-smartypants": "^2.0.5",
    "gatsby-source-filesystem": "^2.0.16",
    "gatsby-transformer-json": "^2.1.7",
    "gatsby-transformer-remark": "^2.2.0",
    "gatsby-transformer-sharp": "^2.1.10",
    "lodash": "^4.17.11",
    "prismjs": "^1.15.0",
    "prop-types": "^15.6.2",
    "react": "^16.7.0",
    "react-adsense": "^0.0.6",
    "react-dom": "^16.7.0",
    "react-helmet": "^5.2.0",
    "react-icons": "^3.3.0",
    "react-typography": "^0.16.18",
    "riffy-gjs-embeded-video": "^1.3.1",
    "typeface-merriweather": "0.0.54",
    "typeface-montserrat": "0.0.43",
    "typography": "^0.16.17"
  },
  "devDependencies": {
    "babel-eslint": "^10.0.1",
    "eslint": "^5.12.0",
    "eslint-config-prettier": "^3.5.0",
    "eslint-config-standard": "^12.0.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-node": "^8.0.1",
    "eslint-plugin-prettier": "^3.0.1",
    "eslint-plugin-promise": "^4.0.1",
    "eslint-plugin-react": "^7.12.3",
    "eslint-plugin-standard": "^4.0.0",
    "prettier": "^1.14.2"
  },
  "homepage": "https://blog.rayriffy.com",
  "keywords": [
    "gatsby",
    "gatsbyjs",
    "react",
    "reactjs",
    "es6",
    "blog"
  ],
  "license": "MIT",
  "main": "n/a",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/rayriffy/rayriffy-blog.git"
  },
  "scripts": {
    "dev": "cross-env GATSBY_ENV=development gatsby develop --https",
    "dev-staging": "cross-env GATSBY_ENV=staging gatsby develop --https",
    "dev-prod": "cross-env GATSBY_ENV=production gatsby develop --https",
    "lint": "./node_modules/.bin/eslint --ext .js,.jsx --ignore-pattern public .",
    "test": "echo \"Error: no test specified\" && exit 1",
    "format": "prettier --trailing-comma es6 --no-semi --single-quote --write 'src/**/*.js' 'src/**/*.md'",
    "develop": "cross-env GATSBY_ENV=development gatsby develop --https",
    "start": "npm run develop",
    "build": "cross-env GATSBY_ENV=production gatsby build",
    "build-staging": "cross-env GATSBY_ENV=staging gatsby build",
    "deploy": "gatsby build --prefix-paths",
    "fix-semi": "eslint --quiet --ignore-pattern node_modules --ignore-pattern public --parser babel-eslint --no-eslintrc --rule '{\"semi\": [2, \"never\"], \"no-extra-semi\": [2]}' --fix gatsby-node.js"
  }
}

gatsby-node.js

Source
const _ = require('lodash')
const Promise = require('bluebird')
const fs = require('fs')
const path = require('path')
const {createFilePath} = require('gatsby-source-filesystem')

exports.createPages = ({graphql, actions}) => {
  const {createPage} = actions

  var siteUrl

  return new Promise((resolve, reject) => {
    resolve(
      graphql(
        `
          {
            site {
              siteMetadata {
                siteUrl
              }
            }
            allMarkdownRemark(
              sort: {fields: [frontmatter___date], order: DESC}
            ) {
              edges {
                node {
                  fields {
                    slug
                  }
                  frontmatter {
                    title
                    subtitle
                    status
                    author
                  }
                }
              }
            }
            allCategoriesJson {
              edges {
                node {
                  key
                  name
                  desc
                }
              }
            }
            allAuthorsJson {
              edges {
                node {
                  user
                }
              }
            }
            lifestyle: allMarkdownRemark(
              filter: {frontmatter: {category: {regex: "/lifestyle/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
            misc: allMarkdownRemark(
              filter: {frontmatter: {category: {regex: "/misc/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
            music: allMarkdownRemark(
              filter: {frontmatter: {category: {regex: "/music/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
            programming: allMarkdownRemark(
              filter: {frontmatter: {category: {regex: "/programming/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
            review: allMarkdownRemark(
              filter: {frontmatter: {category: {regex: "/review/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
            tutorial: allMarkdownRemark(
              filter: {frontmatter: {category: {regex: "/tutorial/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
            rayriffy: allMarkdownRemark(
              filter: {frontmatter: {author: {regex: "/rayriffy/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
            SiriuSStarS: allMarkdownRemark(
              filter: {frontmatter: {author: {regex: "/SiriuSStarS/"}}}
            ) {
              edges {
                node {
                  frontmatter {
                    status
                  }
                }
              }
            }
          }
        `,
      )
        .then(result => {
          siteUrl = result.data.site.siteMetadata.siteUrl
          var filteredresult
          if (
            process.env.GATSBY_ENV === 'production' ||
            process.env.GATSBY_ENV === 'staging'
          ) {
            filteredresult = {
              data: {
                allMarkdownRemark: {edges: null},
                allCategoriesJson: {edges: null},
                allAuthorsJson: {edges: null},
                lifestyle: {edges: null},
                misc: {edges: null},
                music: {edges: null},
                programming: {edges: null},
                review: {edges: null},
                tutorial: {edges: null},
                rayriffy: {edges: null},
                SiriuSStarS: {edges: null},
              },
            }
            filteredresult.data.allMarkdownRemark.edges = result.data.allMarkdownRemark.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.lifestyle.edges = result.data.lifestyle.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.misc.edges = result.data.misc.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.music.edges = result.data.music.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.programming.edges = result.data.programming.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.review.edges = result.data.review.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.tutorial.edges = result.data.tutorial.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.rayriffy.edges = result.data.rayriffy.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.SiriuSStarS.edges = result.data.SiriuSStarS.edges.filter(
              a => a.node.frontmatter.status === 'published',
            )
            filteredresult.data.allCategoriesJson.edges =
              result.data.allCategoriesJson.edges
            filteredresult.data.allAuthorsJson.edges =
              result.data.allAuthorsJson.edges
          } else if (process.env.GATSBY_ENV === 'development') {
            filteredresult = result
          }
          return filteredresult
        })
        .then(result => {
          if (result.errors) {
            console.error(result.errors)
            reject(result.errors)
          }

          const posts = result.data.allMarkdownRemark.edges
          const catrgories = result.data.allCategoriesJson.edges
          const authors = result.data.allAuthorsJson.edges

          var filter
          const postsPerPage = 5
          if (
            process.env.GATSBY_ENV === 'production' ||
            process.env.GATSBY_ENV === 'staging'
          ) {
            filter = 'draft'
          } else if (process.env.GATSBY_ENV === 'development') {
            filter = ''
          }

          // Create blog lists pages.
          const numPages = Math.ceil(posts.length / postsPerPage)

          _.times(numPages, i => {
            createPage({
              path: i === 0 ? `/` : `/pages/${i + 1}`,
              component: path.resolve('./src/templates/blog-list.js'),
              context: {
                limit: postsPerPage,
                skip: i * postsPerPage,
                status: filter,
                numPages,
                currentPage: i + 1,
              },
            })
          })

          // Create blog posts pages.
          var count = 0
          var jsonFeed = []
          _.each(posts, (post, index) => {
            const previous =
              index === posts.length - 1 ? null : posts[index + 1].node
            const next = index === 0 ? null : posts[index - 1].node

            if (count < 5) {
              jsonFeed.push({
                name: post.node.frontmatter.title,
                desc: post.node.frontmatter.subtitle,
                slug: siteUrl + post.node.fields.slug,
              })
            }

            createPage({
              path: post.node.fields.slug,
              component: path.resolve('./src/templates/blog-post.js'),
              context: {
                author: post.node.frontmatter.author,
                slug: post.node.fields.slug,
                previous,
                next,
              },
            })
            count++
          })

          fs.writeFile('public/feed.json', JSON.stringify(jsonFeed), function(
            err,
          ) {
            if (err) {
              console.error(err)
              reject(err)
            }
          })

          // Create category pages
          var categoryPathPrefix = 'category/'
          _.each(catrgories, category => {
            var totalCount = result.data[category.node.key].edges.length
            var numCategoryPages = Math.ceil(totalCount / postsPerPage)
            var pathPrefix = categoryPathPrefix + category.node.key
            _.times(numCategoryPages, i => {
              createPage({
                path: i === 0 ? pathPrefix : pathPrefix + `/pages/${i + 1}`,
                component: path.resolve('./src/templates/category.js'),
                context: {
                  category: category.node.key,
                  currentPage: i + 1,
                  limit: postsPerPage,
                  numPages: numCategoryPages,
                  pathPrefix,
                  regex: '/' + category.node.key + '/',
                  skip: i * postsPerPage,
                  status: filter,
                },
              })
            })
          })

          // Create author pages
          var authorPathPrefix = 'author/'
          _.each(authors, author => {
            var totalCount = result.data[author.node.user].edges.length
            var numAuthorPages = Math.ceil(totalCount / postsPerPage)
            var pathPrefix = authorPathPrefix + author.node.user
            _.times(numAuthorPages, i => {
              createPage({
                path: i === 0 ? pathPrefix : pathPrefix + `/pages/${i + 1}`,
                component: path.resolve('./src/templates/author.js'),
                context: {
                  author: author.node.user,
                  currentPage: i + 1,
                  limit: postsPerPage,
                  numPages: numAuthorPages,
                  pathPrefix,
                  regex: '/' + author.node.user + '/',
                  skip: i * postsPerPage,
                  status: filter,
                },
              })
            })
          })
        }),
    )
  })
}

exports.onCreateNode = ({node, actions, getNode}) => {
  const {createNodeField} = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({node, getNode})
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

PS. I will drop repository here github.com/rayriffy/rayriffy-blog

Thread Thread
 
darcyrayner profile image
Darcy Rayner

It happens in Firefox as well. I think that bug is probably in that gatsby plugin. I've seen service workers use HTTP 2 for loading content before.

Collapse
 
ajmalafif profile image
Ajmal Afif

Hi Nick,

I just checked your site I believe you still haven't solve this, I am testing out some stuffs the _headers and netlify.toml. Will report back if that works!

Collapse
 
cbetta profile image
Cristiano Betta

Did you ever figure this out?

Collapse
 
cbetta profile image
Cristiano Betta

Ignore me, I just spotted that lighthouse is due to be updated to solve this after mid-April

Collapse
 
swyx profile image
swyx
Collapse
 
nickytonline profile image
Nick Taylor

I use Netlify CMS with Gatsby. I believe that includes the gatsby-plugin-netlify package? Either way, I guess I just need to configure it as per the docs?