Create Gatsby and Wordpress portfolio website step by step

Author
April 06, 2022

In this article i will explain you step by step to create gatsby portfolio website using wordpress as headless cms

gatsby-wordpress

Step 1: Creating Wordpress Website

In order to do a local setup for the wordpress download local wp from this link Local Wp and you can create your wordpress website there

wordpress-setup

Step 2: Install necessary plugin required for running graphql query

In order to run graphql query and also for accessing data from gatsby website we need to install plugin so that we can access wordpress data as REST API from gatsby website. here are the list of plugins that we need to install and activate

  1. Advance Custom Fields

  2. WP Gatsby

  3. WP Graphql

  4. WPGraphQL for Advanced Custom Fields : this plugin you need to download externally and upload from this link WPGraphQL for Advanced Custom Fields

wordpress-plugin-setup

Step 3: Create and Install custom theme in wordpress

Here we are going to create our own theme and install it in wordpress, this theme will prevent anyone from accessing wordpress link so that wordpress act only as headless cms

  • In your wordpress local which you have installed previously to create wordpress site you will find an option to go to site folder just click that link and you will be automatically navigated to the files and folder for your wordpress site

  • In your wordpress website folder just navigate to app->public->wp-content->themes

  • Inside themes folder create a your theme folder with any name of your choice i’m going to name it as wp-gatsby-js-theme

  • Once you have created your theme folder just navigate to that folder and create following files

    1. functions.php

    2. index.php

    3. style.css

    4. portfolio_under_content.php : this file is custom template file for our custom post type

    5. Any theme image of your choice just name it as screenshot.png

  • Once you have created the above file just copy paste following code

Leave the file style.css blank in index.php just add php open and close tag

Paste the following code in functions.php

<?php
add_theme_support( 'custom-logo' );
add_theme_support( 'menus' );
add_theme_support('post-thumbnails');

function add_nav_menus() {
    register_nav_menus( array(
        'nav menu'=>'Navigation Bar',
        'footer menu'=> 'Footer Bar',
    ));
}
add_action('init', 'add_nav_menus');

function create_custom_portfolio_post_type(){
	register_post_type('portfolio', array(
	    'labels'=>array(
			'name'=>__('Portfolio'),
			'singular_name'=>__('Portfolio')
		 ),
		'public'=> true,
		'show_in_graphql' => true,
		'graphql_single_name' => 'Portfolio',
        'graphql_plural_name' => 'Portfolios',
		'show_in_admin_bar'=> true,
		'show_in_rest' => true
	));

	add_post_type_support('portfolio', array(
	    'thumbnail',
		'excerpt'
	));
}

add_action('init', 'create_custom_portfolio_post_type');
?>

Paste the following code in portfolio_under_content.php

  <?php /* Template Name: Portfolio Items below content */ ?>
  • Once you have added the respective code login to wordpress admin and activate the theme

Step 4: Add Post to your custom post type portfolio, add pages, add menus in wordpress

  • Once you have activated the theme above you will be able to see a menu with the name Portfolio just click on that then you will be able to add new portfolio with images and excerpt please add 1 or two portfolios with featured images and excerpt

menu-item

  • Create page such as portfolio, home etc by navigating to pages -> add new

page

  • When you create portfolio page make sure to select template as Portfolio under content as custom template

page

  • Create Advance custom fields and make sure to select all the settings as show in screen shot below

page

page

page

  • Create menu for your header by navigating to appearance -> menu link in sidebar, make sure all the settings are same as shown in screen shot below

menu-item

Step 5: Create Gatsby portfolio website and install necessary plugins

Create a fresh gatsby website using following steps

  • Install gatsby cli using following command
npm install --save gatsby-cli
  • Create gatsby website using following command
gatsby new 'your-website-name'
  • Navigate to the path of your newly created project and install the following package
npm install --save gatsby-source-graphql bluebird styled-components
  • Go to your project folder and find gatsby-config.js and add this piece of code to your plugins array
 {
      resolve: `gatsby-source-graphql`,
      options: {
        typeName: "WPGraphQL",
        fieldName: "wpcontent",
        url: `http://gatsbywordpress.local/graphql`,
      },
    },
  • Go to your project folder again and find gatsby-node.js and just copy and replace the whole code with the following code
const _ = require(`lodash`);
const Promise = require(`bluebird`);
const path = require(`path`);
const slash = require(`slash`);

// Implement the Gatsby API “createPages”. This is
// called after the Gatsby bootstrap is finished so you have
// access to any information necessary to programmatically
// create pages.
// Will create pages for WordPress pages (route : /{slug})
// Will create pages for WordPress posts (route : /post/{slug})
exports.createPages = ({ graphql, actions }) => {
  const { createPage, createRedirect } = actions;
  createRedirect({
    fromPath: "/",
    toPath: "/home",
    redirectInBrowser: true,
    IsPermanent: true,
  });
  return new Promise((resolve, reject) => {
    // The “graphql” function allows us to run arbitrary
    // queries against the local WordPress graphql schema. Think of
    // it like the site has a built-in database constructed
    // from the fetched data that you can run queries against.

    // ==== PAGES (WORDPRESS NATIVE) ====
    graphql(
      `
        {
          wpcontent {
            pages {
              edges {
                node {
                  id
                  slug
                  status
                  template {
                    templateName
                  }
                  title
                  content
                }
              }
            }
          }
        }
      `
    )
      .then((result) => {
        if (result.errors) {
          console.log(result.errors);
          reject(result.errors);
        }

        // Create Page pages.
        const pageTemplate = path.resolve("./src/templates/page.js");
        const portfolioUnderContentTemplate = path.resolve(
          "./src/templates/portfolioUnderContent.js"
        );
        // We want to create a detailed page for each
        // page node. We'll just use the WordPress Slug for the slug.
        // The Page ID is prefixed with 'PAGE_'
        _.each(result.data.wpcontent.pages.edges, (edge) => {
          // Gatsby uses Redux to manage its internal state.
          // Plugins and sites can use functions like "createPage"
          // to interact with Gatsby.

          createPage({
            // Each page is required to have a `path` as well
            // as a template component. The `context` is
            // optional but is often necessary so the template
            // can query data specific to each page.
            path: `/${edge.node.slug}/`,
            component: slash(
              edge.node.template &&
                edge.node.template.templateName ===
                  "Portfolio Items Below Content"
                ? portfolioUnderContentTemplate
                : pageTemplate
            ),
            context: edge.node,
          });
        });
      })
      // ==== END PAGES ====

      // ==== PORTFOLIO ====
      .then(() => {
        graphql(
          `
            {
              wpcontent {
                portfolios {
                  edges {
                    node {
                      content
                      portfolio {
                        portfolioUrl
                      }
                      title
                      slug
                      link
                      featuredImage {
                        node {
                          sourceUrl
                        }
                      }
                      excerpt
                    }
                  }
                }
              }
            }
          `
        ).then((result) => {
          if (result.errors) {
            console.log(result.errors);
            reject(result.errors);
          }
          const portfolioTemplate = path.resolve(
            "./src/templates/portfolio.js"
          );
          // We want to create a detailed page for each
          // post node. We'll just use the WordPress Slug for the slug.
          // The Post ID is prefixed with 'POST_'
          _.each(result.data.wpcontent.portfolios.edges, (edge) => {
            createPage({
              path: `/portfolio/${edge.node.slug}/`,
              component: slash(portfolioTemplate),
              context: edge.node,
            });
          });
        });
      })
      // ==== END PORTFOLIO ====

      // ==== BLOG POSTS ====
      .then(() => {
        graphql(
          `
            {
              wpcontent {
                posts {
                  edges {
                    node {
                      excerpt
                      id
                      date
                      title
                      content
                      slug
                    }
                  }
                }
              }
            }
          `
        ).then((result) => {
          if (result.errors) {
            console.log(result.errors);
            reject(result.errors);
          }

          const posts = result.data.wpcontent.posts.edges;
          const postsPerPage = 2;
          const numberOfPages = Math.ceil(posts.length / postsPerPage);
          const blogPostListTemplate = path.resolve(
            "./src/templates/blogPostList.js"
          );

          Array.from({ length: numberOfPages }).forEach((page, index) => {
            createPage({
              component: slash(blogPostListTemplate),
              path: index === 0 ? "/blog" : `/blog/${index + 1}`,
              context: {
                posts: posts.slice(
                  index * postsPerPage,
                  index * postsPerPage + postsPerPage
                ),
                numberOfPages,
                currentPage: index + 1,
              },
            });
          });

          const pageTemplate = path.resolve("./src/templates/page.js");
          _.each(posts, (post) => {
            createPage({
              path: `/post/${post.node.slug}`,
              component: slash(pageTemplate),
              context: post.node,
            });
          });

          resolve();
        });
      });
    // ==== END POSTS ====
  });
};
  • Go to your project folder again and find templates folder or create a folder templates if it doesn’t exist inside src folder and create following templates

  • Go to your project folder and Create template blogPostList.js under src->templates folder and copy paste the following code

import React from "react";
import Layout from "../components/layout";
import { Link } from "gatsby";
import styled from "styled-components";
import Seo from "../components/seo";

const Pagination = styled.div`
  display: flex;
  justify-content: flex-end;
`;

const PageNumberWrapper = styled.div`
  border: 1px solid #eee;
  background: ${(props) => (props.isCurrentPage ? "#eee" : "white")};
`;

const PageNumber = styled(Link)`
  display: block;
  padding: 8px 16px;
`;

const BlogList = ({ pageContext }) => (
  <Layout>
    <Seo title={"Blog"} />
    {pageContext.posts.map((post) => (
      <div key={post.node.wordpress_id}>
        <h3 dangerouslySetInnerHTML={{ __html: post.node.title }} />
        <small>{post.node.date}</small>
        <p dangerouslySetInnerHTML={{ __html: post.node.excerpt }} />
        <div>
          <Link to={`/post/${post.node.slug}`}>Read more</Link>
        </div>
      </div>
    ))}
    <Pagination>
      {Array.from({ length: pageContext.numberOfPages }).map((page, index) => (
        <PageNumberWrapper
          key={index}
          isCurrentPage={index + 1 === pageContext.currentPage}
        >
          <PageNumber to={index === 0 ? "/blog" : `/blog/${index + 1}`}>
            {index + 1}
          </PageNumber>
        </PageNumberWrapper>
      ))}
    </Pagination>
  </Layout>
);

export default BlogList;
  • Go to your project folder Create template page.js under src->templates folder and copy paste the following code
import React from "react";
import Layout from "../components/layout";
import Seo from "../components/seo";
//import PortfolioItems from "../components/PortfolioItems"

const Page = ({ pageContext }) => (
  <Layout>
    <Seo title={pageContext.title} />
    <h1 dangerouslySetInnerHTML={{ __html: pageContext.title }} />
    <div dangerouslySetInnerHTML={{ __html: pageContext.content }} />
  </Layout>
);

export default Page;
  • Go to your project folder Create template portfolio.js under src->templates folder and copy paste the following code
import React from "react";
import Layout from "../components/layout";
import styled from "styled-components";
import Seo from "../components/seo";

const FeaturedImage = styled.img`
  max-width: 300px;
  margin: 16px 0;
`;

const portfolio = ({ pageContext }) => (
  <Layout>
    <Seo title={pageContext.title} />
    <h1>{pageContext.title}</h1>
    <strong>Website url:</strong>
    <a
      href={pageContext.portfolio.portfolioUrl}
      target="_blank"
      rel="noopener noreferrer"
    >
      {pageContext.portfolio.portfolioUrl}
    </a>
    <div>
      <FeaturedImage src={pageContext.featuredImage.node.sourceUrl} />
    </div>
    <div dangerouslySetInnerHTML={{ __html: pageContext.content }} />
  </Layout>
);

export default portfolio;
  • Go to your project folder Create template portfolioUnderContent.js under src->templates fodler and copy paste the following code
import React from "react";
import Layout from "../components/layout";
import PortfolioItems from "../components/PortfolioItems";
import Seo from "../components/seo";
const PorfolioUnderContent = ({ pageContext }) => (
  <Layout>
    <Seo title={pageContext.title} />
    <h1 dangerouslySetInnerHTML={{ __html: pageContext.title }} />
    <div dangerouslySetInnerHTML={{ __html: pageContext.content }} />
    <PortfolioItems />
  </Layout>
);

export default PorfolioUnderContent;
  • Go to your project folder Create templates post.js under src->templates folder and copy paste the following code
import React from "react";

const BlogPost = ({ pageContext }) => {
  return (
    <div>
      <h1>{pageContext.title}</h1>
    </div>
  );
};

export default BlogPost;

Step 5: Update the layout of your gatsby website

We need to add layout and other necessary component to make our site fully functional

  • Updating layout Go to your project folder and finsd components folder and inside that folder create file layout.js and just copy paste the following code
import React from "react";
import MainMenu from "./MainMenu";
import styled, { createGlobalStyle } from "styled-components";

const GlobalStyles = createGlobalStyle`
  @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i');

  body, html{
    font-family: 'Open Sans', sans-serif;
    margin: 0 !important;
    padding: 0 !important;
  }
`;

const LayoutWrapper = styled.div`
  max-width: 960px;
  margin: 0 auto;
`;

const Layout = ({ children }) => (
  <div>
    <GlobalStyles />
    <MainMenu />
    <LayoutWrapper>{children}</LayoutWrapper>
  </div>
);

export default Layout;
  • Updating menu Go to your project folder and finsd components folder and inside that folder create file MainMenu.js and just copy paste the following code
import React from "react";
import { graphql, StaticQuery, Link } from "gatsby";
import styled from "styled-components";
import SiteInfo from "./SiteInfo";

const MainMenuWrapper = styled.div`
  display: flex;
  background-color: #212529;
`;

const MainMenuInner = styled.div`
  max-width: 960px;
  margin: 0 auto;
  display: flex;
  width: 960px;
  height: 100%;
`;

const MenuItem = styled(Link)`
  color: white;
  display: block;
  padding: 16px 16px;
`;

const MainMenu = () => (
  <StaticQuery
    query={graphql`
      {
        wpcontent {
          menus(where: { slug: "main-menu" }) {
            nodes {
              menuItems {
                edges {
                  node {
                    url
                    description
                    label
                    path
                  }
                }
              }
            }
          }
        }
      }
    `}
    render={(props) => {
      return (
        <MainMenuWrapper>
          <MainMenuInner>
            <SiteInfo />
            {props.wpcontent.menus.nodes[0].menuItems.edges.map((item) => (
              <MenuItem to={`${item.node.path}`} key={item.node.label}>
                {item.node.label}
              </MenuItem>
            ))}
          </MainMenuInner>
        </MainMenuWrapper>
      );
    }}
  />
);

export default MainMenu;
  • Updating portfolio items Go to your project folder and finsd components folder and inside that folder create file PortfolioItems.js and just copy paste the following code
import React from "react";
import { graphql, StaticQuery, Link } from "gatsby";
import styled from "styled-components";

const PortfolioItemsWrapper = styled.div`
  display: flex;
  justify-content: center;
`;

const PortfolioItem = styled.div`
  width: 300px;
  border: 1px solid #efefef;
  padding: 16px;
  margin: 16px;
`;

const PortfolioImage = styled.img`
  max-width: 100%;
`;

const PortfolioItems = () => {
  return (
    <StaticQuery
      query={graphql`
        {
          wpcontent {
            portfolios {
              edges {
                node {
                  content
                  title
                  slug
                  link
                  featuredImage {
                    node {
                      sourceUrl
                    }
                  }
                  excerpt
                }
              }
            }
          }
        }
      `}
      render={(props) => (
        <PortfolioItemsWrapper>
          {props.wpcontent.portfolios.edges.map((portfolioItem) => (
            <PortfolioItem key={portfolioItem.node.id}>
              <h2>{portfolioItem.node.title}</h2>
              <PortfolioImage
                src={portfolioItem.node.featuredImage.node.sourceUrl}
                alt="Thumbnail"
              />
              <div
                dangerouslySetInnerHTML={{ __html: portfolioItem.node.excerpt }}
              />
              <Link to={`/portfolio/${portfolioItem.node.slug}`}>
                Read more
              </Link>
            </PortfolioItem>
          ))}
        </PortfolioItemsWrapper>
      )}
    />
  );
};

export default PortfolioItems;
  • Updating seo template used for seo purpose Go to your project folder and finsd components folder and inside that folder create file seo.js and just copy paste the following code
/**
 * SEO component that queries for data with
 *  Gatsby's useStaticQuery React hook
 *
 * See: https://www.gatsbyjs.com/docs/use-static-query/
 */

import * as React from "react";
import PropTypes from "prop-types";
import { Helmet } from "react-helmet";
import { useStaticQuery, graphql } from "gatsby";

function Seo({ description, lang, meta, title }) {
  const { site } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
            description
            author
          }
        }
      }
    `
  );

  const metaDescription = description || site.siteMetadata.description;
  const defaultTitle = site.siteMetadata?.title;

  return (
    <Helmet
      htmlAttributes={{
        lang,
      }}
      title={title}
      titleTemplate={defaultTitle ? `%s / ${defaultTitle}` : null}
      meta={[
        {
          name: `description`,
          content: metaDescription,
        },
        {
          property: `og:title`,
          content: title,
        },
        {
          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: title,
        },
        {
          name: `twitter:description`,
          content: metaDescription,
        },
      ].concat(meta)}
    >
      <link
        href="https://fonts.googleapis.com/css?family=Open+Sans"
        rel="stylesheet"
      />
    </Helmet>
  );
}

Seo.defaultProps = {
  lang: `en`,
  meta: [],
  description: ``,
};

Seo.propTypes = {
  description: PropTypes.string,
  lang: PropTypes.string,
  meta: PropTypes.arrayOf(PropTypes.object),
  title: PropTypes.string.isRequired,
};

export default Seo;
  • Updating Site Info to be diaplyed in header Go to your project folder and find components folder and inside that folder create file SiteInfo.js and just copy paste the following code
import React from "react";
import { graphql, StaticQuery } from "gatsby";
import styled from "styled-components";

const SiteInfoWrapper = styled.div`
  flex-grow: 1;
  color: white;
  margin: auto 0;
`;

const SiteTitle = styled.div`
  font-weight: bold;
`;

const SiteInfo = () => (
  <StaticQuery
    query={graphql`
      {
        wpcontent {
          generalSettings {
            title
            url
            description
          }
        }
      }
    `}
    render={(props) => (
      <SiteInfoWrapper>
        <SiteTitle>{props.wpcontent.generalSettings.title}</SiteTitle>
        <div>{props.wpcontent.generalSettings.description}</div>
      </SiteInfoWrapper>
    )}
  />
);

export default SiteInfo;

Step 6: Run the gatsby website and wordpress website

Run the gatsby website using command

 npm start

Visit your local url and congratulations just now you have created full fledged gatsby and wordpress website