The New Gatsby Homepage Starters - Less Is More

andrews1022

Andrew Shearer

Posted on March 13, 2022

The New Gatsby Homepage Starters - Less Is More

GatsbyConf was just last week, March 2nd & 3rd 2022, and it was a blast! A ton of informative talks, workshops, and being able to talk directly with sponsors was something I've never done before and thoroughly enjoyed and I can't wait for it to happen again next year!

During the conference, a couple of things in particular that were announced piqued my interest:

  • TypeScript support - between v4.8 & v4.9, we can now have the Gatsby API files (gatsby-browser, gatsby-srr, gatsby-config, and gatsby-node) using .ts without any extra configuration!
  • New CMS starters - new headless CMS starters were released for Contentful, DatoCMS, Drupal, & WordPress!

So, I recently decided to sit down and try out the new Contentful Homepage Starter, as that is the CMS I am most familiar with. And what I found was rather interesting.

I truly enjoy using Gatsby to build Jamstack websites. Integrating a headless CMS like Contentful is so simple to do thanks to Gatsby's plugin ecosystem. From my experience, it basically boils down to:

  • Adding the plugin to gatsby-config
  • Double checking I entered in the correct API keys

And it just works seamlessly. Gatsby makes it so simple, which I greatly appreciate.

This post outlines my experience with the new Contentful Homepage starter. I am going to provide my honest and upfront feedback and I hope that it is found helpful. If I say anything that is incorrect from a technical perspective, then please let me know and I will absolutely correct that mistake.

Setup

The setup for the starter was very straightforward. I created a new folder in my projects directory, downloaded the starter using npx, ran the setup command, entered in my Contentful API keys, started up the local dev server, and I was up and running (more detailed steps can be found on the GitHub page, of course)!

At this point I will mention that I cleared out my existing Contentful space of all the content and content models I had previously created. It was merely a playground for testing things, and had no real purpose, so I didn't mind. The setup script also recommends an empty space too anyways.

Next, I opened the project in VS Code and I noticed the typical starting files and folders for a Gatsby project. However, once I started digging a bit deeper, I noticed quite a few things which I thought were quite atypical. And once I opened Contentful in my browser, I couldn't help but be a bit surprised.

Contentful Overload

During setup, I noticed it was taking longer than I expected to generate & import the content into Contentful. That's because compared to the Contentful Gatsby Blog Starter, there is A LOT more content being created here. Like, a lot. With this new homepage starter, you get in total:

  • 33 content models
  • 113 pieces of content
  • 55 pieces of media (images, icons, etc.)

Compared to the Contentful Gatsby Blog Starter:

  • 2 content models
  • 4 pieces of content
  • 4 pieces of media

That is quite a significant difference. So, why is there so much content at the start here? The answer to this question, I think, can be found at the top of the README:

"This starter demonstrates how to use Contentful to build a homepage and can be customized to match your own visual branding."

The keyword here, I think, is "demonstrates". This is supposed to be a demonstration of how to use Contentful with Gatsby. But after having gone through the code and looking at the abundance of generated content, I found myself trying to answer yet another question:

"Who is this starter for?"

The answer to this question, for me, is:

I don't know.

Generic Content

One glaring issue with the generated content in Contentful is that most of it is tied too heavily to a specific page. With this homepage starter, while we have in total 9 links in the main navigation, we only get 2 fully built pages: Home and About.

Which is fine, no problem there. However, out of the 33 generated content models:

  • 17 are for the Homepage (not including the page itself)
  • 6 are for the About page (again, not including the page itself)

Combined, that means approximately 70% of the content models are tied to specific parts of a specific page. Now, one possible reason for this might be to very explicitly tell devs where everything is exactly. However, I feel like there are 2 major issues with this approach, aside from it being rather hand-holding:

  • Price
  • Reusability

Price

Let's tackle price first. If you head over to Contentful's pricing page, you'll quickly realize that Contentful is NOT cheap once you move out of the free Community tier. I'll be nice and tell you if you're too lazy to look: it's $490 USD per month. Yes, that's right, per month. Now, granted that is intended for a team, but still, quite the jump from $0 to $490. Or for me as a Canadian, that is ~$625/month! Granted, again, it is intended for a team and you likely have a source of income to offset the cost.

However, the reason why I bring this up is because with the Community tier, AND with the Team tier actually, you are limited to only 48 different content models. And if you recall, I previously said that this starter comes with 33 content models out of the box. So, that means you are already using up ~68% of your total allowed content models. Therefore, this pattern of creating models for specific parts of a specific page simply cannot sustain itself.

Reusability

This is where the issue of reusability comes in. I think it would be better to create more generic named content models, as it would better highlight their reusability by placing the same types of content on multiple pages throughout the site.

Not only this, but there is some duplication with the existing content models as well. There is Homepage Stat List and About Stat List. What's the difference here? The difference is on the Homepage, there is some extra/surrounding content, and on the About page it is just the group of stats in a row. Why not create a single generic Stat List model, and reference a Stat List piece of content either in a different model or just display it in a simple row/grid wherever you want?

I really like some of the existing models, such as Blog Post, NavItemGroup, Page, SocialLink, and the 3 Layout models (although a minor thing, I would change LayoutFooter and LayoutHeader to just Footer and Header. I think we all know what a Footer and Header are). These provide great flexibility and are highly reusable, which is fantastic! Perhaps consider making some more generic models like:

  • CTA
    • Have fields for the text and the slug where the CTA should take you
  • Author
    • Have this as a reference field for Blog Post
  • [SOMETHING] Group
    • In this model you reference as many of the individual content models as you like
    • For example, Stat Group. You then have a field to reference as many individual Stats as you like
    • Now you have created a single Group which can be reused wherever you like
    • Do the same for things like Cards (Icon, Heading, Copy, Link), Team Members (Picture, Name, Role in Company), and more

So, I think to truly show the power of Gatsby + Contentful, it would be wise to consider:

  • Creating more generic pieces of content
  • Building out more of the pages within the starter using these pieces of content

The benefits of this are:

  • I can see how reusable pieces of content can be
  • I can change which pieces of content I want to show on a given page (using the Page model) that were already in place, or I can add new ones that I create

Ultimately, I think there is too much content being created at the start here. There is, I think, a pretty decent chance you will end scrapping a decent portion of these models and content. Or, you will have to spend a lot of time restructuring/renaming it to meet your project's needs, which is not ideal. On the other hand, the existing contentful/starter-gatsby-blog I think has too little content. Therefore, I think there needs to be a nice middle ground with the quantity of content being generated out of the box.

Lastly, while I like the idea behind the About Page and Homepage models, this isn't exactly ideal. Due to the limitation of the number of content models you can create, this pattern will not be able to continue much longer (creating models for individual pages). Something I have tried to do before is create a single Page model and render that model using a single template file in the code. This model would have fields like:

  • Title
  • Slug (automatically generated based off the Title)
  • Content or Sections (reference as many pieces of content as you wish. Things like 50/50's, Groups, Hero's, etc., to structure the various blocks/sections for a page as needed)

Some issues with this approach though are:

  • This gets kinda sloppy with GraphQL, as you have 1 really large query at the bottom of the template file. I suppose you could try breaking it up with fragments to make it a bit cleaner, though
  • You need to make sure to have a ComponentToRender field on all of the models you wish to use as a section

You can see my approach here (at the time I already had a Page model, so I named it Dynamic Page just for testing as an FYI). We can use React.lazy() to dynamically import and render each component based on the value of the ComponentToRender field, and pass along the content as a prop. You then set the styles/props/types/etc. for each component as you normally would.

I like this approach as it allows you to create a single template for (theoretically) an endless number of pages. Again, the GraphQL query would become incredibly long and rather difficult to maintain.

Another drawback to this setup though is that the workflow has a lot of steps that you have to do each time:

  • Create the model
  • Ensure the model has a ComponentToRender field (you can set a default value to save you some time from entering in a value each time you create a Page)
  • Create/style/type the component
  • Add it to the long GraphQL query in the template file

Where I do think this approach might come in handy is if you have a parent page, say Services, and then you have multiple pages for each Service where the order of the sections on each page is the exact same, just that the content is different. So, you can create a single template like I did, and this ensures that the GraphQL query is of a certain length, remains consistent, and is easier to maintain.

The Code

Regarding the code of this starter, I'm just going to list a few things I noticed and outline some areas that I think could be improved:

Organize scripts in package.json

This is something that annoys me a bit not with this starter, but most projects in general. Put your scripts in alphabetical order! It makes it so much easier to find/edit/update/add scripts to the list.

Organize dependencies in package.json

I've personally never understood why we need both dependencies AND devDependencies. Either way, you depend on that package. Why the separation? It seems highly redundant to me. Why not group all of them together into a single dependencies object and order them alphabetically to keep things nice and tidy?

Create React App made the decision to consolidate everything to into dependencies a few years agi, and you can see Dan Abramov's explanation here. I of course agree with the reasons he provided, but in all reality, if you want to move packages out of dependencies into devDependencies, you are more than free to do so.

Organize components into their own folders

This starter comes with 22 different component files. I understand everyone has their preferred approach for organizing components (and that may be why things are as they are as devs will move things around as they please anyways), but a common approach I see mentioned online often is:

src/
  components/
    ComponentA/
      ComponentA.js - The actual React component
      ComponentA.styles.js - The Styled Components file for the component (could also be .css or .scss module, of course)
      ComponentA.test.js - The tests for the component
      ComponentA.stories.js - The Storybook file for the component
    ComponentB/
      ComponentB.js
    ComponentC/
      ComponentC.js
    etc.
Enter fullscreen mode Exit fullscreen mode

I think when you have this many components all grouped together, it looks disorganized. Gatsby is an opinionated framework, and therefore it makes sense to have an opinion on how components should be organized. But if this is their opinion on how things should be organized, having nothing in folders, then that is pretty disappointing, and I would strongly encourage Gatsby to reconsider.

Lastly, I noticed there was a Fallback component being used in both the home and about page files. I think it would be wise to create a single Fallback component in the components folder and import it from there to cut down on duplication of code.

UI Components

In addition to the point above, there is a file in the components folder called ui.js. In this one file, there are 28 UI components. Now, having all your components all in a single file should make it easier to maintain, in theory. In practice, this also feels highly disorganized. I think it's best to keep a 1:1 ratio: 1 component to 1 file. So, the structure would then ideally be something like this:

src/
  components/
    ...
    ui/
      Base
      Container
      Flex
      etc.
Enter fullscreen mode Exit fullscreen mode

The import would then look something like:

import Base from 'components/ui/Base'

Which I think is totally fine! This is similar to how you import components from Material UI, a popular React UI library.

Finally, I found the code in ui.js difficult to understand. I'm not entirely sure what is going on for some of the components, specifically the cx & Base components. I think it would be helpful to at least include some comments noting what their purpose is and/or how they work.

Conditional Rendering

I would suggest not using && for conditional rendering. You should be using the ternary operator with the desired 'or' case, or just return null. The reasons why are explained here by Kent C. Dodds in a blog post.

Spacing

A lot of the code is not spaced enough, I think. For example, the component caret.js.

I think there should be a line break between the imports and the function, as well as a line break between const height ... and return. I personally find that when code is spaced out a bit, it makes it much easier to read.

Custom Hooks

I would like to suggest moving the uses of useStaticQuery into their own hooks. For example, the footer and header components. Extract these hook calls out and wrap them in their own custom hooks, such as useFooter() and useHeader(). The benefits of this are that it shows devs how to use custom hooks if they have little or no experience doing so before, and it makes the function code a lot leaner and easier to read.

Props Spreading

I would like to strongly encourage prop spreading to be removed where it is being used. The reason for this I think is explained perfectly on the react/jsx-props-no-spreading ESLint page:

"Enforces that there is no spreading for any JSX attribute. This enhances readability of code by being more explicit about what props are received by the component. It is also good for maintainability by avoiding passing unintentional extra props and allowing React to emit warnings when invalid HTML props are passed to HTML elements."

This leads into my next point, which is regarding TypeScript.

TypeScript

As mentioned near the beginning of this post, between Gatsby v4.8 & v4.9, we can now have the Gatsby API files using .ts without any extra configuration. Which is fantastic! I was so happy to see this update!

However, with the heavy use of props spreading, I think it might lead to some frustration/confusion over how to type each of the components props, as well as typing out values returned via GraphQL queries (although, there are ways of doing the latter automatically with tools like GraphQL Code Generator).

Furthermore, it would be great to have an all TypeScript version of not only this starter, or these new homepage starters, but for all official Gatsby starters. As far as I can tell, this is only true for gatsby-starter-minimal. And while we can have our entire Gatsby project using TypeScript, we still do not get type checking in the CLI. Hopefully this comes in a future release.

gatsby-config

A minor point, but in gatsby-config.js, there is require('dotenv').config() twice, one with and one without an options object argument. Not sure why that is the case, as the second one with the options object being passed in is perfectly fine on its own.

Also, if you implement ESLint into your project, you will get the import/no-extraneous-dependencies error. This error appears when you are using a specific package not listed in your package.json. Gatsby uses dotenv under the hood, so everything will work just fine, but I think it is best to include it in package.json.

Optional

Now, just a short list of some minor things you may or may not be looking to do, or have to do, depending on your/your teams workflow:

  • Replace react-feather with something like Font Awesome or Material UI Icons, or another icon library
  • Replace @vanilla-extract with styled-components, emotion, or other CSS-in-JS library, or .css/.scss modules
  • Convert all components to use function expression syntax if that is your preference
  • Change import * as React from 'react'; to import React from 'react';
    • This way you can stop using React.something(), and just import what you need (ie. hooks): import React, { useState } from 'react';

Going Forward

So, let's try and answer this question from before:

"Who is this starter for?"

Based off the time I have spent with this, I believe the answer to this question is a developer who is:

  • New to Gatsby (or React in general)
  • New to not just Contentful, but building Jamstack apps/using headless CMSs in general

Now, there is nothing wrong with creating a starter for that type of developer. However, if you meet that criteria, you might:

  • Find the amount of pre-generated content overwhelming
  • Like that content models are separated by page. However, as outlined earlier, both the Community and Team tiers for Contentful have a limit to how many models you can create, and Contentful is not cheap

If you are an experienced dev, and have your own preferred workflow, you will find yourself spending a lot time initially updating/refactoring both the code and the content in Contentful.

If you work for an agency, you/your team may or may not:

  • Have your own starter that you may or may not maintain
  • Have a go-to list of NPM packages for certain functionality. Therefore, you may or may not have to heavily modify this starter
  • Take this starter and modify it to your teams workflow, and have your own custom starter
    • Or you can start from scratch each time for a new project/client. But if you create your own custom starter, you then have to maintain it / update it

So, how do we move forward from here?

I think there are a few options to consider:

  • Refactor this starter both content and code wise
  • Create slightly different versions of these starters with varying quantities of content for varying skill levels
  • Make TypeScript only versions of these starters

In the end, these are just my opinions and my opinions alone. Gatsby could absolutely just ignore what some random person is saying about them on the internet. However, I do truly hope you’ve found the opinions I have expressed here in this post at least a little helpful. And even if Gatsby took 1 small, minor thing I've mentioned here and implemented it, I would take immense pride in knowing that. If anyone from the Gatsby team would like to get in touch with me to discuss any of the points I've made, or if I can assist in any other way, you can reach out to me on Twitter and I would be more than happy to discuss things there.

Thank you for reading.

💖 💪 🙅 🚩
andrews1022
Andrew Shearer

Posted on March 13, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related