Learning Outcomes
- assess whether a website is better suited as a dynamic or static site
- use a static site generator to create a static website
- deploy a static static site to a hosting service tuned for serving static sites
Resources
Lab
Video walkthrough of this lab.
Review Dynamic Page Generation with Express
Fork this project in GitLab: https://gitlab.com/langarabrian/courses2
.
After forking, go to “Settings | General | Advanced” and remove the fork relationship.
Go to “Settings | General | Visibility …” and make the project visibility private and save the changes.
Start up your Google Cloud Shell.
Clone the project using “git clone {your project SSH URL}”.
In MongoDB Atlas, create a “courses” collection where documents have the following shape:
Then you should be able to install the dependencies and run the application as follows:
1
2
3
4
npm install
export MONGODBURI='mongodb+srv://<USERNAME>:<PASSWORD>@<HOSTNAME>'
export MONGODBNAME='<DBNAME>'
PORT=NNNN DEBUG=courses2:* npm run dev
Make sure that you understand how this application works before continuing to the next step.
Replacing Dynamic Page Generation with Static Site Generation using Gatsby
In this section, we are going to re-implement the course catalogue appplication above using the Gatsby static site generator.
Instal the Gatsby CLI:
1
npm i -g gatsby-cli
Fork this project in GitLab: https://gitlab.com/langarabrian/gatsby-courses2
.
After forking, go to “Settings | General | Advanced” and remove the fork relationship.
Go to “Settings | General | Visibility …” and make the project visibility private and save the changes.
Clone the project using “git clone {your project SSH URL}”.
Make sure the project runs as expected:
1
2
3
cd gatsby-courses2
npm install
PORT=8080 gatsby develop
You should be able to preview the running application. The index page is supposed to display the list of courses in the program. Right now there is just an empty table. We are going to pull the data from our MongoDB Atlas database. The first step is is to install and enable the MongoDB plugin for Gatsby:
1
npm i -s gatsby-source-mongodb
then add the following in the plugins
array in gatsby-config.js
:
1
2
3
4
5
6
7
8
9
10
11
12
{
resolve: `gatsby-source-mongodb`,
options: {
connectionString: process.env.MONGODBURI,
dbName: process.env.MONGODBNAME,
collection: `courses`,
extraParams: {
retryWrites: true,
w: "majority",
},
},
},
Restart the development server and verify the site still works:
1
PORT=8080 gatsby develop
Use the GraphiQL browser to browse the data for the site. You get access to the GraphiQL browser by putting /___graphql
after the hostname in the browser address bar. There should be tree browser in the left-hand side and you should be able to open up “allMongodbCpsc2650Courses | edges | node” and then check “subject”, “course”, and “title”. You should see the GraphQL query previewed in the middle column. You can run the query and see the results by clicking the “play” button in the tool bar. You should see the contents of the collection in the far right column.
NOTE: “allMongodbCpsc2650Courses” may be different for you, but it should be of the form “allMongodbDddddCcccc” where “Ddddd” is the capitalized name of your database and “Ccccc” is the capitalized name of your collection.
Copy the GraphQL query from the middle column to your clipboard.
We are going to modify the index page to display the list of courses. The first step is to bring in the graphql
module at the top of src/pages/index.js
:
1
import { graphql } from "gatsby";
Then, place the query to get all the courses at the bottom of src/pages/index.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
export const query = graphql`
query MyQuery {
allMongodbCpsc2650Courses {
edges {
node {
subject
course
title
}
}
}
}
`
(Remember, you may have to adjust the above depending on your database and collection names.)
Then inject the data resulting from the query into the page rendering function. Just change the function definiton so that it accepts the { data }
argument:
1
export default function Home({ data })
Finally, we can fill in the <tbody>
element with the code to iterate over all the courses:
1
2
3
4
5
6
7
{data.allMongodbCpsc2650Courses.edges.map(({ node }, index) => (
<tr key={index}>
<td>{node.subject}</td>
<td>{node.course}</td>
<td><a href={'/course/' + node.subject + '/' + node.course}>{node.title}</a></td>
</tr>
))}
Test the index page and ensure that it displays the list of courses:
Now we need to programmatically generate the course detail pages. This involves 3 steps:
- creating a template for the course detail page
- decorating each data node that is going to become a course detail page with a URL slug
- generating the final pages
Create the course detail template page, src/templates/course.js
as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from "react";
export default function Course() {
return (
<div>
<h1>SSSS NNNN blah blah bah</h1>
<table>
<tr>
<th>Subject</th>
<td>SSSS</td>
</tr>
<tr>
<th>Course</th>
<td>NNNN</td>
</tr>
<tr>
<th>Title</th>
<td>blah blah blah</td>
</tr>
<tr>
<th>Credits</th>
<td>N</td>
</tr>
<tr>
<th>Description</th>
<td>description goes here</td>
</tr>
</table>
</div>
);
}
Decorate each node that is going to become a course detail page by creating a file, gatsby-node.js
as follows:
1
2
3
exports.onCreateNode = ({ node, actions }) => {
console.log( node.internal.type);
}
This code doesn’t actually decorate the desired nodes, it just logs type of every node as they are created. Restart the development server and watch the extra output in the console.
We aren’t interested in all the nodes, just the course nodes, so modify the code as follows:
1
2
3
4
5
exports.onCreateNode = ({ node, actions }) => {
if (node.internal.type === `mongodbCpsc2650Courses`) {
console.log( node.internal.type);
}
}
and restart the development server. Now that we have targetted the right nodes, we can finally decorate them with the desired URL slug which is going to based on the subject code and course number (e.g. /course/CPSC/1030/
). Make these final changes and restart the development server:
1
2
3
4
5
6
7
8
9
10
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === `mongodbCpsc2650Courses`) {
createNodeField({
node,
name: `slug`,
value: `/course/${node.subject}/${node.course}/`,
});
}
}
You can check that the slugs have been set correctly using GraphiQL.
Now that the nodes have the slug data, we need to generate the actual pages. At the top of the gatsby-node.js
file, import the path
module:
1
const path = require(`path`);
Then, at the bottom of the file, define the createPages
function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
exports.createPages = async ({ graphql, actions }) => {
// **Note:** The graphql function call returns a Promise
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise for more info
const { createPage } = actions;
const result = await graphql(`
query {
allMongodbCpsc2650Courses {
edges {
node {
fields {
slug
}
}
}
}
}
`);
result.data.allMongodbCpsc2650Courses.edges.forEach(({ node }) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/course.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: node.fields.slug,
},
})
});
}
The first part of the function queries for all the slugs. The second part iterates over all the slugs and generates a page for each one using the template. If you make this change and restart the development server, you should see that all the links on the index page work now except that all the pages display the same static text.
The final step is update the template to display the actual course data. At the top of src/templates/course.js
, import the graphql
module:
1
import { graphql } from "gatsby";
At the bottom of the file, add a query to get the data for the specific course:
1
2
3
4
5
6
7
8
9
10
11
export const query = graphql`
query($slug: String!) {
mongodbCpsc2650Courses(fields: { slug: { eq: $slug } }) {
subject
course
title
credits
description
}
}
`
Notice the use of the $slug
variable. This was sent from our call to createPage
in gatsby-node.js
. Finally, update the render function to use the data values resulting from the query:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export default function Course({ data }) {
const course = data.mongodbCpsc2650Courses;
return (
<div>
<h1>{course.subject} {course.course} {course.title}</h1>
<table>
<tr>
<th>Subject</th>
<td>{course.subject}</td>
</tr>
<tr>
<th>Course</th>
<td>{course.course}</td>
</tr>
<tr>
<th>Title</th>
<td>{course.title}</td>
</tr>
<tr>
<th>Credits</th>
<td>{course.credits}</td>
</tr>
<tr>
<th>Description</th>
<td>{course.description}</td>
</tr>
</table>
</div>
);
}
Verify that everything is working in the browser preview.
Now we can generate the actual static site. Stop the development server and enter the following command:
1
gatsby build
This generates the static site in the public
directory. Gatsby includes a static web server that you can use to verify the site is going to work correctly as built:
1
gatsby serve -p 8080
Refresh the web preview and verify that it still works. Look over the public directory and you can see that there are individual .html files for each course.
Deploying a Static Site
There are many options for deploying a static site in production. In this exercise we will use Firebase Hosting. Head over to https://firebase.google.com/ and sign up / sign in with your Google account. Then go to Firebase console and add a new project. DO NOT ENABLE GOOGLE ANALYTICS.
Back in your Google Cloud Shell terminal, install the Firebase CLI tools:
1
npm install -g firebase-tools
Then sign in to Google:
1
firebase login --no-localhost
Because you are using the --no-localhost
flag, you will see a URL that will have to copy and paste manually into a new browser tab. You will then be prompted to select a Google account. Then you will see a token that you will have to copy and paste back into the terminal.
Be sure you are at the root directory of your gatsby-courses2
project and intilaize Firebase for the project:
1
firebase init
When you are asked to select features, only choose “Hosting”. Under “Project Setup”, choose “Use existing project” and select the project you created above. When asked about the “public directory” go with the default (public). When asked to configure as a single page app, choose “No”. When asked to overwrite “public/index.html”, choose “No”.
Finally, deploy the site as follows:
1
firebase deploy
When the deploy operation is finished it will give you a URL where you can access the site.
Assignment
- Create another MongoDB Atlas collection that has a list of instructors. Include properties such as name, title, phone, and office. You can some sample data from the Computing Science Faculty List.
- Add a new page to the
gatsby-courses2
project that displays the list of instructors. - Implement CI/CD (
.gitlab-ci.yml
) for thegatsby-courses2
project. If you are using the stocknode:alpine
image, you will have install the Gatsby CLI during the build phase and the Firebase CLI during the deploy phase. Also, you will have to arange to have the MONGODBURI and MONGODBNAME environment variables available during the build phase. Also, you will need a FIREBASE_TOKEN to use during the deploy phase; you can generate one using thefirebase login:ci --no-localhost
command. You will need to use theartifacts:paths
parameter in the build job so that thepublic
directory created by the build job is available to the deploy job.