project / April 24th, 2021
This is a portfolio website that I built using the GatsbyJS front-end framework, and has two main components: a projects page to showcase the projects I have worked on, and a blog. The GraphQL API is used to pull content (projects and blog posts) from the Contentful Content Management System (CMS) and populate both of these components with it. There is also a home page that will contain updates for upcoming new content and an about page for more information about myself.
Gatsby is a framework based off React and GraphQL that makes it easy to create static websites or dynamic web applications. Besides the obvious goal of creating a website to showcase my projects, I also wanted to learn about Gatsby and how GraphQL is used to pull data from external data sources like a Content Management System (CMS) and Markdown. This project presented an excellent opportunity to use numerous concepts and tools I have learned from the 2021 Complete Web Development Bootcamp, The Great Gatsby Bootcamp, and some MERN stack videos I watched after completing the Web Development Bootcamp. I was especially curious to see how all the pieces from each of these resources fit together.
GatsbyJS
GraphQL
Contentful Content Management System
Markdown
React
HTML/JSX
CSS (and SCSS)
Material-UI
React Reveal
particles.js
React Typed
React Helmet
Originally, the base of the website was created by following most of the Great Gatsby Bootcamp, and contained a home, about, contact, and blog page. However, after learning how to use a CMS and how to dynamically pull data from it to form the Blog, I removed the contact page and applied the same principles to create a Projects page instead. Unlike the blog page, the links in the projects page were more visual, displayed as panels by using Card components from the Material-UI library.
I also applied my own knowledge of React, HTML, and CSS to change the structure of the blog and and style it to slightly resemble chorus.fm, which is a music news website that I frequently browse (see below).
In terms of the overall look of the website, the original website had no colour and very little styling. I wanted to retain the minimalist look, but combine it with a summer feel. Inspiration for the website colours were taken from a favourite picture that I took back in 2015:
I used particles.js to add animated particles to the home page that resembled bubbles. I also incorporated the "Karla" font from Google Fonts instead of the generic sans-serif, and used the React Reveal library to add subtle fade animations whenever a page is loaded.
In terms of the website structure, I personally preferred the simple multi-page structure used in the bootcamp, where there are dedicated pages for each of its different sections. In my experience, this makes for a better user experience than a single-page website that requires having to scroll to different sections, or uses a shaky scrolling animation to move to a section after clicking a section link. To enable the user to scroll back to the top of a page and use the navigation bar, I added a button that appears when a user has scrolled deep enough into the page.
High-level Overview:
In part 1, the tasks are geared toward creating the items that are past the "Build" step in the above image. Gatsby, HTML/JSX, CSS/SCSS, and React are used to create a basic version of the portfolio (albeit in development mode and not yet deployed to production) with empty home, about, contact, and blog pages.
In parts 2 and 3, I move behind the "Build" step and look at the data sources for the website, which can either be Content Management Systems (CMSs), Markdown blog posts in my project directory, built-in siteMetadata, or third-party data sources. Gatsby's GraphQL API is used to pull in this data and incorporate it into the website to form the blog posts. At first, siteMetadata and Markdown blog posts are explored. But eventually, the flaws of each data source ultimately lead to the use of the Contentful CMS instead. By the end of part 3, a functional blog that pulls post data from Contentful is built.
In part 4, the blog posts are further customized to include images and videos. By this point, I start to diverge from the material in the Great Gatsby Bootcamp and really begin to make the site my own by expanding it to include a projects page, rather than having a contact page. The same principles used to build the blog are also used to build the projects page, and are combined with my knowledge of Material-UI to display each project as a panel after it is fetched from Contentful.
In part 5, the home and about pages are completed. Finishing touches are also made to improve the look of the entire website and the website is deployed using Netlify.
After generating a Gatsby project using the Gatsby command line interface, JavaScript files are created in a pages directory to store the home, about, contact, and blog components. Next, header and footer components (which will be shared by every page) are created in the components directory. The footer contains the name of the website author and the current year from a JavaScript Date object, while the Header is a navigation bar that uses the Gatsby Link component to link to all four pages in the website. This is followed by creating a Layout component that will incorporate the Header and Footer. Placed between the header and footer will be the unique content for each page, which is accessed through the children prop that contains the JSX from each page. The Layout component serves as a universal layout that each page will import so they all have the same layout. At this point, I have created a simple static Gatsby website.
The next step is to apply basic styles to the website. Before applying styles, a Gatsby plugin to enable Sass is installed and configured into the project. Sass enables the user the option of using SCSS syntax in addition to CSS. This allows the use of features that don't exist in CSS like variables, nesting, and loops, which can be useful in managing a stylesheet as it gets larger. After applying a base set of styles to all pages, the styling for each page is further customized by using CSS modules. CSS modules are stylesheets that contain specific styles for each component, and allow the targeting of specific elements (like the links in the Header navigation bar) so that they can be styled differently from the base set of styles. Once I have become comfortable with CSS modules, I used my CSS knowledge to apply the styling I want for each page.
By using Gatsby's GraphQL API, dynamic data can be pulled into and used in the portfolio website. GraphQL pulls this data from sources like a Content Management System (CMS) or a Markdown file that is locally stored in the project directory. Without GraphQL, the blog would require manually adding a page to the pages directory for each blog post and writing the blog content as component JSX, which is not ideal.
To start, I explore one of the easiest built-in ways to set up dynamic data - pulling it in from siteMetadata. This is simply an object in my Gatsby configuration file that can have key-value pairs for anything I want - fields such as website title, author, or my initials. The GraphQL playground editor is accessed in the browser and used to write a query to fetch data, like the initials field I created in siteMetadata. Once it is confirmed that the query is successful and fetches my initials, the same query is written in the Header component file using the graphql and useStaticQuery methods from the Gatsby library. This allows the Header React component to access the initials in my siteMetadata, which are then displayed in the navigation bar and used as a link. The same concept is applied to fetch the author name in my Footer component and display it dynamically in the footer of my website.
While siteMetadata is useful for small pieces of data, it's not great for storing the blog posts I want. This leads to the idea of using Markdown blog posts. In the project directory, a folder is created to store Markdown files with a front matter (a block with key-value pairs for fields like title or date) and post content. The goal is to list the title and date of each post as links on the blog page, and then use a blog post template file to dynamically generate separate pages for each blog post.
To transform the post title and date from Markdown front matter to HTML before it is rendered, the gatsby-source-filesystem plugin is first installed and configured to pull the Markdown files into Gatsby. This is followed by installing the gatsby-transformer-remark plugin. It is now possible to write GraphQL queries to fetch the list of Markdown blog posts (useful for the blog page) and an individual post. Once the list of blog posts is fetched, the map function can be used to iterate through each post and display each post title and date on the blog page.
To dynamically generate an individual page for each post, the first step is to generate a slug (a string containing the Markdown file name) for each post. A slug makes it possible to setup a unique URL (eg. /slug or /blog/slug), where the content of the blog is accessible. To do this, new configuration file is made to enable use of the APIs in Gatsby that allow us to manage nodes (data structures that store data, like a Markdown post). The API used is onCreateNode, which has a method called createNodeField that makes it possible to attach a slug field to a node representing a Markdown file. Once each node has a slug, the second step is to create the blog post template, which is nothing more than a standard React component. In this part, I began to customize my blog post template to resemble the posts seen on chorus.fm (seen below).
To use the template and slug to generate a new page for each blog post, another node API called createPages is used. When iterating through the list of Markdown posts, calling the createPage method from createPages for each post will dynamically render a page using the blog post template as an argument. The slug for each post is also used as an argument to ensure that each page has a unique URL endpoint. In the blog page component, the JSX is adjusted so that there is a Link to each of these newly created posts.
At this point, I am not yet rendering anything specific about the blog post like title, date, and content in the template. To insert these pieces of dynamic data in the blog post, a dynamic GraphQL query that uses the slug for each post as an argument is written above the Blog post template and exported. Exporting allows Gatsby to run this query and provide the post data as a prop for the Blog post template. The post title, date, and content can now be accessed and rendered in any manner I wish. I now have a website with a Markdown blog, all powered by Gatsby.
Although creating a Markdown blog is useful and can be a viable solution for many, Gatsby can also source content from a CMS. In this case, I am connecting my portfolio to the Contentful CMS. After creating an account on Contentful, a space is created. This space enables me to create a data model for my blog posts, which contains fields like title, slug, published date, body, and a preview (which was a custom field I created to store an excerpt from the body). Compared to Markdown posts, this is already a significant improvement because fields like the slug can be easily created for a content model without having to use the onCreateNode node API like before. Once the blog post model is created, I can create as many blog posts (which are instances of that model) as I wish in my Contentful space. Another advantage that Contentful has over Markdown is that it has its own word processor, and all I have to do is simply fill in the fields I created before publishing it.
Once a blog post is published in Contentful, the gatsby-source-contentful plugin is installed and configured with the space ID and access token, so that GraphQL can be used to access it. In GraphQL Playground, new queries are now available to fetch either individual posts or a list of all posts from Contentful. Similar to the Markdown queries, the Contentful queries allow access to nodes that represents blog posts and contain the fields I specified. First, a new Contentful query (instead of the previous Markdown query) is written in the Blog component to fetch a list of all Contentful blog posts, and sorted by date in descending order. The resulting list of posts (and their titles, slugs, dates, and preview excerpts) from the query are then accessed and mapped to be rendered in the Blog page, with my own personal styling applied to resemble the main page of Chorus.fm:
At this moment, clicking a link from this list to open a separate page for the full blog post doesn't work. To dynamically generate these pages, I first removed the onCreateNode API since it was used to create slugs for Markdown posts, and slug is now a field I already set up in Contentful. The createPages API is still used, but slightly adjusted to fetch the Contentful slugs and use them with the blog post template. Once this is done, the query in the blog post template is adjusted to pull in post data from Contentful instead of Markdown. The title, date, and body (with the help of the rich-text-react-renderer library) fields that were created in Contentful are then accessed, rendered, and styled in the template as seen below:
After accomplishing this task, I start to branch off from the work done in the Great Gatsby Bootcamp and really start to customize my portfolio. First, I wanted to learn how to properly add an image to my blog posts, since the methods in the bootcamp did not work. Research was done on StackOverflow and the Contentful help section, and I learned that a library called rich-text-types can allow access to embedded assets (which is what images are added as in Contentful). A search is performed for the ID of the image in the blog post data, then the URL of the image is grabbed and used to render an HTML image element.
Similar to how I wanted to render images in my blog posts, I also wanted to learn how to embed YouTube videos, which was extremely difficult to figure out. With the help of fellow Contentful users in the help section who were encountering similar issues, I learned that the rich-text-types library also enabled access to all hyperlinks in the blog post data. Then, an IF statement is written to check if each hyperlink contains the base URL that embedded YouTube videos have. If it does, the corresponding YouTube video is rendered as an iframe HTML element for display in the blog post. Otherwise, it renders as a regular hyperlink.
Next, I wanted to expand my website and apply the concepts I used to build the blog in order to build a projects page (instead of a contact page) for displaying my projects. In Contentful, I created a content model for projects, which had fields for the title, slug, date, body, description, topics, and a main image. The project page template will render the body, which is a write up of the background, scope, process, and lessons learned from the project. In the projects page, the link to each project will contain the title of that project, a brief description, the main image, and a list of topics/technologies covered in that project.
Just like how I previously fetched the list of all Contentful blog posts, a query is written in the newly made Projects component to fetch a list of all Contentful projects, which are also sorted by date in descending order. The list of projects (and their titles, slugs, main images, descriptions, and topics list) are then accessed and mapped to be rendered in the Projects page. However, instead of rendering the projects as a list like in the Blog page, I rendered each as project as a Card component from Material-UI (which I learned about during the Floatpad project). Each project link takes on the form of a Card, which I heavily customized and styled to display the main image at the top, the title and description in the middle, and the list of topics at the bottom.
To dynamically generate a project page, I used the createPages API like I did for the blog posts. I fetched a slug from each project, and used it with the newly created Project post template. The image, title, date, and body (with the help of the rich-text-react-renderer library) fields are then accessed, rendered, and styled in the Project template.
After completing the two major components of my portfolio site in the Projects and Blog, I began the process of completing this project by writing content for my home and about pages and styling them. In the Home/Index component, I utilized the Particles component from particles.js to add an particle animation behind the main container. This component is highly customizable, and I adjusted it to resemble blue bubbles bouncing around in a box. Below the main container, I added another container to display updates for upcoming changes to the website. The updates are animated in a loop using the Typed component from the react-typed library. The About component is straightforward, and simply contains a description of myself and my programming journey so far.
In the Footer component, I added Icons from Material-UI that serve as links to my email, LinkedIn profile, and GitHub. I also added a 404 page that will render when a page is not found. The React Helmet plugin is installed in order to use the Helmet component, which allows me to manage the titles shown on the browser tabs (also known as the head of the document) and add a favicon logo. The React Reveal package is installed to add a subtle fade animation whenever a page is loaded. Finally, the "Karla" font package from Google fonts is installed in the project and used as the primary font in my website.
To deploy this Gatsby site in production, Netlify is used to pull the portfolio assets from my GitHub repository and deploy the website.
Building my portfolio site was the culmination of everything I had learned from the Complete 2021 Web Development Bootcamp, and combined with the additional learning about Gatsby and MERN stack post-bootcamp. In this project, I was able to use old skills from React, Material-UI, HTML/JSX, and CSS with new skills from Gatsby, GraphQL, Contentful, and Markdown. I also got to utilize new libraries like React Reveal, React Typed, React Helmet, particles.js to further enhance my website.
On a personal level, I really enjoyed the entire process because it provided an outlet for my problem-solving skills and creativity, which is rare in my experience. This project represents a significant milestone in my exploration of programming and is something I am extremely proud of. Another payoff from this project is that there are always numerous things about the website that I can improve on (see below), which presents new challenges that I can tackle whenever I have the time!
While I am very happy with the website I have produced, there are a several nice-to-haves I would like to try adding when I have extra time:
A carousel/slideshow component that allows the user to slide through all the screenshots in a project write up in one place, preferably at the top of a project page where the main image is. This would probably require having to add an imageList field to my projects data model in Contentful, instead of having just an image field for the main image. The imageList would contain a list of screenshots of the different parts of each project, and a carousel/slideshow component would allow the user to simply click through all the pictures in a project write up rather than have to scroll through the entire article. However, one may argue that having screenshots scattered across a write up is actually helpful, since they help provide context and a visual aid for parts of the write up that might be confusing. There are potential benefits and drawbacks to weigh with this idea, and might require user feedback before making a decision.
A search bar on the projects and blog pages to allow the user to search for a specific project or blog post. This would be especially helpful as the number of projects and blog posts increases.
Paginating the list of projects and blog posts on the projects and blog page. This would be another useful feature to add as the number of projects and blog posts increases.
I considered adding my resume in the about page, but ultimately chose not to as I felt LinkedIn already did a great job doing that. I have also provided numerous links to my LinkedIn profile on all of my pages, and adding this might seem redundant and unnecessarily time-consuming. Privacy was also another consideration, as resumes contain sensitive personal information and I felt it would better that a user contacts me or check my LinkedIn (which is more secure) if they want my resume.
As I add more blog posts to my website, it would be nice to tag or categorize each of them under different topics to make searching for them easier.
From user feedback so far, one major improvement that was needed is a button to scroll the user back to the top of the page. I immediately implemented this by using useState to manage the state of the button to be visible or not visible depending on if the user has scrolled past a certain amount of pixels.
Another important lesson I learned was to ensure that my portfolio site was compatible with other major internet browsers like Safari, Google Chrome, and Microsoft Edge, in addition to Firefox. To make components such as my project panels, YouTube videos, and scroll button compatible for all screen sizes, I used media queries in their SCSS files to specify different styling at various breakpoints for different screen sizes. I also discovered an issue in which the particles from the particles.js library were pixelated and covered my navigation bar in the Google Chrome browser, which led me to find an improved way to style the particle canvas that would all work for all browsers. Below are screenshots of the website on mobile:
Portfolio Site (You should already be in it!)