An Extensive Comparison of Gatsby & Next.js (While Rebuilding My Portfolio)
Tags: gatsby, nextjs, typescript, contentful
Published: 2022-04-15 15:58:54

I was looking at my developer portfolio recently and decided it could use an update. While I kept it up to date with my most recent projects that I wanted to showcase, I wasn't a fan of the look anymore. I also thought it would be nice to use [Contentful](https://www.contentful.com/) as a [headless CMS](https://en.wikipedia.org/wiki/Headless_content_management_system) to manage the content of the site, as well as fetch and display a couple of my most recent blog posts from here on dev.to.

I've mostly used [Gatsby](https://www.gatsbyjs.com/) both at work and for personal projects, and I've been wanting to learn more about [Next.js](https://nextjs.org/) and become more familiar with using it, so I took this opportunity to start rebuilding my portfolio using both of these frameworks. I had a list of requirements in mind, so along the way I noted my experience with both frameworks and I want to share that experience with you here. Perhaps you will find it helpful the next time you need to decide which framework to use.

## Requirements

I like to keep my portfolio nice and simple. The order of the sections are:

- Hero (including nav)
- Projects
- About Me
- Blog Posts
- Contact
- Footer

It's a single page, so I will not be looking to create dynamic routes / pages for any of my content. I would consider doing so for the blog posts since Contentful has a Markdown text box, and on dev.to you write in Markdown, so it would just be a copy paste. However, I wanted to see what would be considered best practice / convention for both:

- Fetching content from Contentful using GraphQL
- Fetching blog posts from the dev.to API using the good ol' [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)

A few other things I wanted to compare between the two were:

- Downloading and starting with a basic boilerplate with TypeScript support
- Integrating [styled-components](https://styled-components.com/)
- A few points regarding Contentful:
  - The use of environment variables
  - Using webhooks to automatically update the hosted site whenever I make a change in Contentful
  - Similarly, how changes in Contentful appear during local development
  - Local development experience
  - Using fragments for reusable queries
- Optimizing images from Contentful
- How easy is it to host a Gatsby and Next.js site

I also want to note that my intention wasn't to _fully_ rebuild my portfolio with each, but more so I wanted to get the scaffolding done, compare my notes as well as any pros and cons of the requirements I've listed above, and from there I'll decide which one I plan to proceed with to complete the project.

I simply created 2 new branches on my existing portfolio repo, which I aptly named `gatsby` and `next`, and worked with each framework on their respective branch. In hindsight, it would've been _much_ easier to just work with framework each in their own folders in isolation. Oh well! ¯\\\_(ツ)\_/¯

So, let's get started!

## Boilerplate

To start, I looked at what possible starter / boilerplates both frameworks offered. While both offer Contentful based starters, they are not in TypeScript OOTB. So, I decided upon using their basic TypeScript starter and building it up from there. That way, I have as close as a starting point for each as possible.

Next.js has supported TypeScript for a while now, but thankfully Gatsby also now has full TypeScript support too. Not just with pages and components, which they also have supported for a while now, but all of the Gatsby API files (`gatsby-browser`, `gatsby-node`, etc.) can also be written in TypeScript as of Gatsby [v4.8](https://www.gatsbyjs.com/docs/reference/release-notes/v4.8/) and [4.9](https://www.gatsbyjs.com/docs/reference/release-notes/v4.9/).

For Gatsby, you can run the command:

`npm init gatsby -ts`

And for Next.js you can run the command:

`npx create-next-app@latest --ts`

A quick, minor note regarding Gatsby: in the [documentation](https://www.gatsbyjs.com/docs/how-to/custom-configuration/typescript/) it says to run `npm init gatsby` and select TypeScript as your language when prompted. Or you can run `npm init gatsby -ts` to skip that question. In my experience, that is not true. When you run `npm init gatsby -ts`, you still have to select TypeScript as your language. So, it doesn't matter whether or not you include the `-ts` flag.

For Next.js, there is no questionnaire during setup, and I actually don't mind this at all, and it kind of makes sense if you think about it. Gatsby is focused on creating fast, static sites using headless CMSs. As such, it gives you a few options to pick from during setup in the CLI, as well as a few options for styling your app.

With Next, since it is a hybrid framework and it can be used for much more than static headless CMS-based sites, including a questionnaire is not ideal because there are just _so_ many things to consider. Perhaps including a step for styling would be nice, and some other common inclusions such as linting, but I think it's fine without one.

Either way, both startup just fine after running `npm run dev`!

## Styled Components

This is where things start to get interesting. [styled-components](https://styled-components.com/) is my go to approach for styling React apps myself. Otherwise, I usually go with Material UI (now [MUI](https://mui.com/)) or [React Bootstrap](https://react-bootstrap.github.io/).

Implementing styled-components into Gatsby and Next are quite different.

First, let's take a look at Gatsby.

### Gatsby

With Gatsby, you will need to install the following NPM packages:

- `babel-plugin-styled-components`
- `gatsby-plugin-styled-components`
- `styled-components`

And if you're using TypeScript like I do, then the `@types` package from DefinitelyTyped as well:

- `@types/styled-components`

So, in total 3-4 packages. From there, you simply need to add `gatsby-plugin-styled-components` to your `plugins` array inside your `gatsby-config` file. After that, I typically create a `styles` folder inside the `src` folder, and then inside the `styles` folder I create 2 files:

- `GlobalStyle.ts`
- `theme.ts`

`theme.ts` will hold variables for the project such as colours, font sizes, breakpoints, etc. Here's the starting code I typically use:

```js
const theme = {
  mediaQueries: {
    desktopHD: 'only screen and (max-width: 1920px)',
    desktopMedium: 'only screen and (max-width: 1680px)',
    desktopSmall: 'only screen and (max-width: 1440px)',
    laptop: 'only screen and (max-width: 1366px)',
    laptopSmall: 'only screen and (max-width: 1280px)',
    tabletLandscape: 'only screen and (max-width: 1024px)',
    tabletMedium: 'only screen and (max-width: 900px)',
    tabletPortrait: 'only screen and (max-width: 768px)',
    mobileXLarge: 'only screen and (max-width: 640px)',
    mobileLarge: 'only screen and (max-width: 576px)',
    mobileMedium: 'only screen and (max-width: 480px)',
    mobileSmall: 'only screen and (max-width: 415px)',
    mobileXSmall: 'only screen and (max-width: 375px)',
    mobileTiny: 'only screen and (max-width: 325px)'
  },
  shades: {
    white: '#fff',
    black: '#000'
  },
  greys: {
    // add any greys here
  },
  colors: {
    primary: {
      // add primary colors here
    },
    secondary: {
      // add secondary colors here
    }
  },
  fonts: {
    // add font-families here
  },
  fontWeights: {
    thin: 100,
    extraLight: 200,
    light: 300,
    normal: 400,
    medium: 500,
    semiBold: 600,
    bold: 700,
    extraBold: 800,
    black: 900
  },
  fontSizes: {
    // add font sizes here
  }
};

export default theme;
```

And `GlobalStyle.ts` will use the `createGlobalStyle` helper function to create a `GlobalStyle` component. This component will house global styles and any styling resets you want to include. Here is what I typically start with:

```js
import { createGlobalStyle } from 'styled-components';
import theme from './theme';

// destructured theme properties
const { mediaQueries } = theme;

const GlobalStyle = createGlobalStyle`
  html {
    box-sizing: border-box;
    font-size: 100%;

    @media ${mediaQueries.desktopSmall} {
      font-size: 87.5%;
    }
  }

  body {
    /* add custom font-family here */
    line-height: 1;
  }

  *,
  *::before,
  *::after {
    box-sizing: inherit;
    color: inherit;
    font-size: inherit;
    -webkit-font-smoothing: antialiased;
    margin: 0;
    padding: 0;
  }

  button,
  input,
  textarea {
    /* add custom font-family here */
  }

  img,
  svg {
    border: 0;
    display: block;
    height: auto;
    max-width: 100%;
  }

  a {
    &:link,
    &:visited {
      text-decoration: none;
    }

    @media (hover) {
      &:hover,
      &:active,
      &:focus {
        outline: 0;
        text-decoration: underline;
      }
    }
  }

  ol,
  ul {
    list-style: none;
  }

  blockquote,
  q {
    quotes: none;
  }

  blockquote:before,
  blockquote:after,
  q:before,
  q:after {
    content: '';
    content: none;
  }

  table {
    border-collapse: collapse;
    border-spacing: 0;
  }

  audio,
  canvas,
  video {
    display: inline-block;
    max-width: 100%;
    zoom: 1;
  }
`;

export default GlobalStyle;
```

From here, we open the `Layout` component and wrap everything with the `ThemeProvider` component provided by `styled-components`. `ThemeProvider` must have a `theme` prop, so we can import the `theme` above. We will also place the `GlobalStyle` component as a child inside the `ThemeProvider`. In the end, your `Layout` file should look something like this:

```js
const Layout = ({ children, title }) => (
  <ThemeProvider theme={theme}>
    <GlobalStyle />

    <Seo title={title} />

    <Navigation />

    <main>{children}</main>

    <Footer />
  </ThemeProvider>
);
```

And that's it! Now, any styled components we create will have access to our theme via `props.theme` like so:

```js
background-color: ${(props) => props.theme.shades.white};
```

And just as a quick note, we can use destructuring to shorten it a bit:

```js
background-color: ${({ theme }) => theme.shades.white};
```

In the end, a pretty quick setup! Now let's take a look at Next.js.

### Next.js

With Next.js, we install all the same packages we would with Gatsby, except `gatsby-plugin-styled-components` of course.

From there, we can create a `styles` folder and also create the `GlobalStyle.ts` & `theme.ts` files as we did with Gatsby.

Next, while with Next.js you would typically have a `Layout` component, you actually do not want to wrap that component with `ThemeProvider`. Instead, you want to wrap everything in the `_app.tsx` file in the `pages` folder like so:

Before:

```js
const MyApp = ({ Component, pageProps }: AppProps) => <Component {...pageProps} />;

export default MyApp;
```

After:

```js
const MyApp = ({ Component, pageProps }: AppProps) => (
  <ThemeProvider theme={theme}>
    <Layout>
      <GlobalStyle />
      <Component {...pageProps} />
    </Layout>
  </ThemeProvider>
);

export default MyApp;
```

And we're done! Actually, no we're not. At this point, using styled-components will work just fine...sort of. If you either:

- Manually refresh the page in the browser
- Host this project, on Vercel or Netlify, for example

The styles will likely break. This is because the server-side rendering does _not_ fetch the styles before rendering the page. To correct this issue, we need to inject server-side rendered styles to the `head` so it can render the page and its styles correctly.

Inside the `pages` folder, we need to create a file called `_document.tsx`. In that file, you will need this:

```ts
// next
import Document, { Head, Html, Main, NextScript } from 'next/document';
import type { DocumentContext, DocumentInitialProps } from 'next/document';

// styled components
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
  static getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />)
        });

      const initialProps = await Document.getInitialProps(ctx);

      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      };
    } finally {
      sheet.seal();
    }
  };

  render() {
    return (
      <Html>
        <Head>
          <link rel='preconnect' href='https://fonts.googleapis.com' />
          <link rel='preconnect' href='https://fonts.gstatic.com' crossOrigin='true' />
          <link
            href='https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap'
            rel='stylesheet'
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;
```

A **BIG** shoutout to Nicolás M. Pardo here on dev.to as he posted this in a comments section from a different article and it saved the day! And as you can see, I also just added the necessary code to include links to Google Fonts. If you don't need that, you can simply get rid of it.

Now we just need to restart the server and everything should be a-ok!

## Contentful

In this section, we'll take a look at a few key areas:

- The use of environment variables
- Querying data from Contentful using GraphQL
- The use of webhooks
- Local development experience
- Using fragments for reusable queries

### The use of environment variables

Somewhat disappointingly, while going through the setup for Gatsby, after selecting Contentful as your headless CMS, the necessary `.env` files are not created as part of the setup. And with Next, you will need to set them up manually yourself.

Now to be fair to both, if you use the [starter-gatsby-blog](https://github.com/contentful/starter-gatsby-blog) from Contentful themselves, the new [gatsby-starter-contentful-homepage](https://github.com/gatsbyjs/gatsby-starter-contentful-homepage) from Gatsby, or the Next.js Contentful [example](https://github.com/vercel/next.js/tree/canary/examples/cms-contentful), these do use environment variables. It's just these basic starters that do not.

For Next.js, you simply need to create a `.env.local` file, and add in your variables like so:

```
VARIABLE_NAME=VALUE
```

Then restart your local dev server if it is currently running so Next.js can load in the variables. You should see `Loaded from env` in your terminal after the local dev server starts up.

For Gatsby, create 2 files:

- `.env.development`
- `.env.production`

Add in the same variables in both files and you will be good to go.

### Querying data from Contentful using GraphQL

Both frameworks take a similar approach to querying data from Contentful using GraphQL. To _greatly_ oversimplify things, the process essentially boils down to:

- Make a query using GraphQL
- Pass that returned data to your components as props
- Display the data in your components

How each framework does that is of course different, and we'll explore it in further detail below.

But first, one thing I do want to give big props to is Gatsby's implementation of the GraphiQL playground. Specifically, including an Explorer. I have to admit, I have taken this for granted, as it makes structuring queries so, so much easier.

With Next.js, I ended up using the GraphiQL playground provided by Contentful themselves. You can view it here:

https://graphql.contentful.com/content/v1/spaces/YOUR_SPACE_ID_HERE/explore?access_token=YOUR_ACCESS_TOKEN_HERE

Just make sure to update the placeholders with your actual Contentful Space ID and Access Token.

#### Gatsby

With Gatsby, querying data from Contentful using GraphQL is very straightforward. In your page component file, you add your GraphQL query typically beneath the `export default`:

```js
// component code up here...

export default Home;

export const homePageQuery = graphql`
  query HomePageQuery {
    contentfulHero(id: { eq: "ID_HERE" }) {
      title
      tagline
      backgroundImage {
        id
        description
        gatsbyImageData
      }
    }
  }
`;
```

In this example, I'm querying data from a specific Hero model I created, and I want the values from the `title`, `tagline`, and `backgroundImage` fields. I'll be discussing images in more detail later on.

To actually consume this data, the page component can access the returned data via the `data` prop. From there, you can pass it down as a `prop` to the corresponding component like so:

```ts
const Home = ({ data, location }: PageProps<GraphQLResult>) => {
  // just double check...
  console.log('data: ', data);

  return (
    <Layout title='Home'>
      <Hero contentfulData={data.contentfulHero} />
    </Layout>
  );
};
```

With TypeScript, we can also type out how the response will look. The name I've always used is `GraphQLResult`, but you can name it whatever you like. The type for this is just above the component like so:

```ts
type GraphQLResult = {
  contentfulHero: ContentfulHero;
};

const Home = ({ data, location }: PageProps<GraphQLResult>) => {
  // just double check...
  console.log('data: ', data);

  return (
    <Layout title='Home'>
      <Hero contentfulData={data.contentfulHero} />
    </Layout>
  );
};
```

I like to keep my types in a separate folder, so I import `ContentfulHero` wherever I need it. The type looks like this:

```ts
import type { IGatsbyImageData } from 'gatsby-plugin-image';

export type ContentfulHero = {
  title: string;
  tagline: string;
  backgroundImage: {
    id: string;
    description: string;
    gatsbyImageData: IGatsbyImageData;
  };
};
```

And the Hero component can also use this type for `props`:

```ts
type HeroProps = {
  contentfulData: ContentfulHero;
};

const Hero = ({ contentfulData }: HeroProps) => (
  <div className='wrapper'>
    <h1>{contentfulData.title}</h1>
    <p>{contentfulData.tagline}</p>
  </div>
);

export default Hero;
```

This way I have full type checking for my data, as well as some nice auto-completion when using the data in my component.

There are some tools you can use to automatically generate the types for your GraphQL Schema, such as [GraphQL Code Generator](https://www.graphql-code-generator.com/). However, since this is a fairly small project, and I am the only dev working on it, I don't mind setting things up manually myself.

#### Next.js

With Next.js, the process is quite similar. However, there are multiple ways to query data with Next.js:

- React hooks at the component level (`useEffect` with an `async` function is a common approach)
- `getStaticProps()` at the page level
- `getServerSideProps()` at the page level
- `useSWR()` hook at the component level

For my purposes, since this is a static portfolio site, I will be using the `getStaticProps()` function. As outlined in the Next.js [documentation](https://nextjs.org/docs/basic-features/data-fetching/get-static-props), if the data is coming from a headless CMS, then `getStaticProps()` is a great choice. Check out the documentation on how the function works if you are not familiar with it, as I do not have the courage to explain it since I am still new to it.

Inside this function, we can make our request. This [video](https://www.youtube.com/watch?v=bMRQgguzBLs&t=1613s) from the Contentful YouTube channel was very helpful, and required minimal modification for TypeScript.

In the video, Lee recommends using the `graphql-request` package. And I have to say, as a first time user, I quite liked it! I will definitely look to use this again in the future!

To start, we will need to import `GraphQLClient` & `gql` from `graphql-request` as named imports:

```js
import { GraphQLClient, gql } from 'graphql-request';
```

Then inside the `getStaticProps()` function, we can start with the following:

```ts
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`;

const options = {
  headers: {
    authorization: `Bearer ${process.env.CONTENTFUL_ACCESS_TOKEN}`
  }
};

const graphQLClient = new GraphQLClient(endpoint, options);
```

Notice the value for `endpoint` is different than that of the URL you would visit in the browser!

Next, we can use the `gql` function to write our GraphQL Query, and we'll see some noticeable differences:

```js
const homePageQuery = gql`
  {
    hero(id: "ID_HERE") {
      backgroundImage {
        sys {
          id
        }
        description
        height
        url
        width
      }
      tagline
      title
    }
  }
`;
```

The first is actually `ID_HERE`. When using the Explorer in Gatsby's GraphiQL playground, you can either use the ContentfulId or Id. The latter looks like this:

`a1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1`

Where the As and 1s could be any combination of letters and numbers. With Next.js, you don't get this type of ID. Instead, you simply use the `ENTRY ID` for whichever piece of content you are querying for.

To see this `ENTRY ID`, select any piece of content you have created in Contentful. Then on the right-hand sidebar menu, select the `Info` tab. Second from the top is the `ENTRY ID`. In all fairness, you could also use this in your GraphQL queries with Gatsby as well. I've always just used the long-form id above out of habit.

The next difference are the fields we can query for the image. We'll need these files to pass them on to Next.js's `<Image />` component. Again, we'll discuss each framework's approach to images and image optimization later.

Finally, store the returned data in a variable like so:

```ts
const data: ContentfulResponse = await graphQLClient.request(homePageQuery);
```

Then return in a props object:

```ts
return {
  props: {
    data
  }
};
```

Here, I am using the ES6 object shorthand where if the key and value are the same, we can write like I have above instead of like this:

```ts
return {
  props: {
    data: data
  }
};
```

So all together, `getStaticProps()` looks like this:

```ts
export async function getStaticProps() {
  // get contentful data
  const endpoint = `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`;
  const options = {
    headers: {
      authorization: `Bearer ${process.env.CONTENTFUL_ACCESS_TOKEN}`
    }
  };

  const graphQLClient = new GraphQLClient(endpoint, options);

  const homePageQuery = gql`
    query HomepageQuery {
      hero(id: "ID_HERE") {
        backgroundImage {
          sys {
            id
          }
          description
          height
          url
          width
        }
        tagline
        title
      }
    }
  `;

  const data: ContentfulResponse = await graphQLClient.request(homePageQuery);

  return {
    props: {
      data
    }
  };
}
```

Now in the `Home` page component, we can access this `data` prop like so:

```tsx
const Home: NextPage<HomeProps> = ({ data }) => {
  console.log('data: ', data);

  return (
    <div>
      <Hero />
    </div>
  );
};
```

And to be clear, `data` is just the name I chose. I could've called it `hero` if I wanted to, and accessed it via a `hero` prop instead. I like using `data` because I'll be adding on to the GraphQL query later on querying for multiple pieces of content. So, `data` is just a generic variable name I like to use, and comes from the habit of using Gatsby.

And just like before with Gatsby, we pass the data down to the corresponding component:

```tsx
const Home: NextPage<HomeProps> = ({ data }) => {
  console.log('data: ', data);

  return (
    <div>
      <Hero contentfulData={data.contentfulHero} />
    </div>
  );
};
```

And that's it! That's how we can query data from Contentful with both frameworks with GraphQL. Personally, I don't really have a preference. I think both are perfectly fine.

And as a quick side note, I would extract the querying logic in `getStaticProps()` into a separate file and folder to make the code inside `getStaticProps()` cleaner, as well as make the query a bit more reusable. It would require some modification, but it would make for a good starting point.

### The use of webhooks

When it comes to hosting, whether you choose Netlify, Gatsby Cloud, or Vercel, they all have options to set up both environment variables, as well as webhooks that you can use with Contentful. This way, whenever you make a change, or mutliple changes, in Contentful, the site will rebuild with the latest data.

### Local development experience

An interesting note here is that with Gatsby, during local development, whenever you make a change in Contentful, it will _not_ appear while the local dev server is running. You must restart the server, and clean the `public` and `.cache` folders as well. So, I would recommend adjusting the `dev` script in `package.json` to the following:

```json
"clean": "gatsby clean",
"dev": "npm run clean && gatsby develop",
```

This is because Gatsby will run your GraphQL queries during build time, and not run time. Therefore, a restart is required.

With Next.js however, you simply need to manually refresh the page in the browser, and any changes will be seen.

### Using fragments for reusable queries

The great thing about querying data with GraphQL is you can use something called [fragments](https://graphql.org/learn/queries/#fragments). Fragments help by cutting down on repeated query fields.

#### Gatsby

With Gatsby, creating reusable fragments is very straightforward. In the `src` folder, I typically create another folder called `graphql`. In the `graphql` folder, I then create a file called `fragments.ts`. Here, let's create a highly reusable fragment for fields for an image:

```ts
export const imageFields = graphql`
  fragment ImageFields on ContentfulAsset {
    id
    description
    gatsbyImageData(placeholder: BLURRED)
  }
`;
```

What's so nice about this is that Gatsby _automatically_ exports these queries to the global scope. So, these fragments are available in any other GraphQL query in the entire project! We can even use our fragments in other fragments!

And just as a quick side note, I like to include `id` here even if I'm only querying for a single image because in the instance I _do_ query for multiple images, I have the `id`'s ready to use for mapping over the array and using them as the value for the `key` prop.

Now, let's say for example we have a standard 50/50 model, with `heading` & `copy` on the left and an `image` on the right. For the fragment, it would look something like this:

```ts
export const fiftyFiftyFields = graphql`
  fragment FiftyFiftyFields on ContentfulFiftyFifty {
    id
    heading
    copy {
      copy
    }
    image {
      ...ImageFields
    }
  }
`;
```

Now we can use this fragment in the `HomePageQuery`, for example, like so:

```ts
export const homePageQuery = graphql`
  query HomePageQuery {
    firstFiftyFifty: contentfulFiftyFifty(id: { eq: "ID_1_HERE" }) {
      ...FiftyFiftyFields
    }
    secondFiftyFifty: contentfulFiftyFifty(id: { eq: "ID_2_HERE" }) {
      ...FiftyFiftyFields
    }
  }
`;
```

In this example, we can also see [aliases](https://graphql.org/learn/queries/#aliases) being used. Aliases let you rename the result of a field to anything you want. This is helpful for when you are querying the same thing but with different arguments.

So, combining fragments and aliases really help make our queries shorter, cleaner, and easier to read and write.

#### Next.js

With Next.js, while we don't get automatic global scoped fragments, we can still create and use them, just with a little bit of extra work.

We can start with the same process by creating a `fragments.ts` file inside of a `graphql` folder. Let's create a reusable Image fragment:

```ts
import { gql } from 'graphql-request';

export const FRAGMENT_IMAGE = gql`
  fragment ImageFields on Asset {
    sys {
      id
    }
    description
    height
    url
    width
  }
`;
```

Now back in the index page file, inside `getStaticProps()` and then inside our `gql` template string, we can go from this:

```ts
const homePageQuery = gql`
  query HomepageQuery {
    hero(id: "ID_HERE") {
      backgroundImage {
        sys {
          id
        }
        description
        height
        url
        width
      }
      tagline
      title
    }
  }

  # fragments
  ${FRAGMENT_IMAGE}
`;
```

To this:

```ts
const homePageQuery = gql`
  query HomepageQuery {
    hero(id: "ID_HERE") {
      backgroundImage {
        ...ImageFields
      }
      tagline
      title
    }
  }

  # fragments
  ${FRAGMENT_IMAGE}
  # make sure to import this!
`;
```

It may not look very powerful right now when querying a single piece of content, but I'm sure you can imagine if this homepage had say 8-10 sections, all of them containing images, having the same fields repeated over and over again would make the query very long and rather difficult to traverse.

## Image Optimization

Both frameworks come with image optimization OOTB in the form of components. Next.js has [\<Image />](https://nextjs.org/docs/api-reference/next/image), and Gatsby has both [\<StaticImage />](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/#staticimage) and [\<GatsbyImage />](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/#staticimage).

Let's explore their differences:

### Gatsby

Gatsby comes with 2 different image components. When you should use which? The answer to that is very simple.

Is the image you're using the same every time the component is rendered? For example, the site logo, or any other picture located within the project locally? Then use `<StaticImage />`.

Is the image you're using going to be passed into a component as a `prop`, and / or coming from an external source, such as Contentful? Then use `<GatsbyImage />`.

Since this article is all about Contentful, we'll be focusing on `<GatsbyImage />`!

In the GraphiQL playground, whenever you want to query for an image (or multiple images), I'd recommend getting the following fields:

- `id`
- `description`
- `gatsbyImageData`

With this, we can create a reusable fragment for querying those fields on images (known as Assets in Contentful):

```ts
export const imageFields = graphql`
  fragment ImageFields on ContentfulAsset {
    id
    description
    gatsbyImageData(placeholder: BLURRED)
  }
`;
```

When you execute the query with these fields, you'll see a _ton_ of data for the gatsbyImageData field. But don't worry, the `<GatsbyImage />` component will handle all of that for us!

You would use that data like so:

```js
<GatsbyImage image={contentfulData.image.gatsbyImageData} alt={contentfulData.image.description} />
```

You can see in my fragment above I also added `(placeholder: BLURRED)`. This will do as the wording suggests! It will blur the image until the full res version is ready to use. This will help cut down on page load time and help improve performance.

### Next.js

With Next.js, we get a single `<Image />` component that we can use for both static and dynamic images. And very similarly to Gatsby, as long as we pass it the right data for its various props, it will handle all the heavy lifting for us!

And just like with Gatsby, we can create a highly reusable fragment like we did above in the previous section to query certain fields from Contentful. We would then use that data like so:

```ts
<Image
  src={data.hero.backgroundImage.url}
  alt={data.hero.backgroundImage.description}
  width={data.hero.backgroundImage.width}
  height={data.hero.backgroundImage.height}
/>
```

And that's it! Well...sort of. With Next.js, there is one more step. We have to add the domain these images are coming from to an array in our `next.config.js` file. This array holds an array of `domains` that are whitelisted. This tells Next.js that it is ok to fetch the images from these external sources and optimize them:

```js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    domains: ['images.ctfassets.net']
  },
  reactStrictMode: true
};

module.exports = nextConfig;
```

Ok, _now_ we have optimized images from Contentful!

## Blog Posts

Now that I'm writing blog posts here on dev.to, I wanted to incorporate them into my portfolio as well. While Contentful does have a Markdown text box that I could just copy and paste the post into and create dynamic routes with either framework, I was more interested in how I would query data from 2 different data sources using 2 different methods:

- Querying content from Contentful using GraphQL
- Querying posts from dev.to using REST API w/ Fetch

Let's take a look!

### Gatsby

With Gatsby, I had an assumption of how I would query for my blog posts, and after some research (aka quick Googling), it seems like my assumption was correct.

I created a BlogPosts component, and inside that component I made the fetch call like so:

```tsx
const BlogPosts = () => {
  const [posts, setPosts] = useState<DevPost[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  const fetchBlogPosts = async () => {
    setIsLoading(true);

    const response = await fetch('https://dev.to/api/articles?username=andrews1022');
    const data: DevPost[] = await response.json();

    setPosts(data);

    setIsLoading(false);
  };

  useEffect(() => {
    fetchBlogPosts();
  }, []);

  return (
    <div>
      <h2>Posts</h2>
      <div>
        {isLoading ? (
          <p>Loading...</p>
        ) : (
          posts.map((post) => (
            <div key={post.id}>
              <p>{post.title}</p>
            </div>
          ))
        )}
      </div>
    </div>
  );
};
```

Now I realize I don't have error handling in the form of an error state, or a cleanup in my `useEffect`, but that's ok since I'm just trying things out before making any large commitments.

This is a pretty standard approach:

- Have state for the data, loading, and error
- Write an async function that queries the data, and updates the various pieces of state accordingly
- In the useEffect hook, call the async function and have an empty array of dependencies so this function will only be executed once when the component is first mounted / rendered

And that's it! My blog post titles display just fine. With Next.js though, that's where things get a little spicy.

### Next.js

With Next.js, we can take advantage of the `getStaticProps()` function and add this additional request below the GraphQL query to Contentful and return it alongside the `data` like so:

```ts
export async function getStaticProps() {
  // make the contentful query up here...

  // get dev.to data
  const postsResponse = await fetch('https://dev.to/api/articles?username=andrews1022');
  const posts: DevPost[] = await postsResponse.json();

  return {
    props: {
      data,
      posts
    }
  };
}
```

Again, I realize there isn't any error handling here, this is just for the sake of demonstration purposes. And in case I didn't mention earlier, the `DevPost` type is a custom type I created.

With these posts, I can pass them down to my BlogPosts component:

```js
<BlogPosts posts={posts} />
```

And then loop over them and display them like so (just the `title` for now to make sure things are working):

```tsx
type BlogPostsProps = {
  posts: DevPost[];
};

const BlogPosts = ({ posts }: BlogPostsProps) => {
  return (
    <div>
      <h2>Posts</h2>
      <div>
        {posts.map((post) => (
          <div key={post.id}>
            <p>{post.title}</p>
          </div>
        ))}
      </div>
    </div>
  );
};
```

Why I think this is spicy is because since Next.js pre-renders the page at build time using the props returned by `getStaticProps()`, there is no awkward `Loading...` text or spinner being shown temporarily. Everything is just...there! I'm still getting used to Next.js, so I probably didn't explain that very well but I just think it's neat (insert [Marge Simpson meme](https://i.kym-cdn.com/photos/images/newsfeed/001/498/880/572.jpg) here)!

## Hosting

Hosting is dead simple no matter which you pick. Each framework has their own hosting service which they recommend:

[Gatsby Cloud](https://www.gatsbyjs.com/products/cloud/) for Gatsby projects, and [Vercel](https://vercel.com/) for Next.js projects.

You could also use [Netlify](https://www.netlify.com/) if you wish.

Whichever you pick, you can select your project from GitHub (once you have given it authorization to do so), and the service will automatically build and host the site for you!

And as previously mentioned, you can include environment variables for your Contentful API keys, as well as setup webhooks with Contentful.

## Final Thoughts

Whew, that was a lot! If you made it this far, thank you so much for reading this post. I had a lot of fun researching and trying out the various points I mentioned here, and it was great to try out and learn more about Next.js some more as well.

My goal for this article was to simply show you each framework's approach for common tasks and let you decide which approach(es) you do or don't like. Personally, I'm going to stick with Next.js to build out the rest of my portfolio simply because I want to become more familiar with it and learn more about it.

In my opinion, for a simple static like this, you really can't go wrong with either framework. If you have more experience with one over the other, I would recommend trying the other and see what you think. I provided my experience here and I hope you found it useful.

Cheers!