The aim of this project is to build a fast staticly hosted, client-side blog. It should get a near perfect Google Lighthouse score to have the best chance of ranking high in the Google search results. This is achieved by building the site in Gatsby.js.
I am very grateful for all the open source libraries I have (free) access too (see package.json). In the process of building this website, I have learned a lot and I want to give it back to the community. Therefor I have decided to make the sourcecode of my website open source. Use it, modify it, improve it, learn from it. As long as you remove my content first, I'm happy to share it with you.
- Gatsby.js: "Gatsby is a React-based open source framework for creating websites and apps."
- MDX: "MDX is an authorable format that lets you seamlessly write JSX in your Markdown documents." Used to write our articles (blog, portfolio, conditions).
- Reactstrap: "Easy to use React Bootstrap 4 components". To make styling the website a piece of cake.
- SASS: We use SASS, SCSS to be precise, to compile our own CSS file. This makes customizing the default Bootstrap values a breeze.
- gatsby-plugin-advanced-sitemap: To generate a sitemap to be read by search engines.
- gatsby-plugin-robots-txt: To guide crawlers and search engines to your sitemap.
- react-helmet and react-seo-component: To help us with our SEO.
- Preact: To minimize our React bundle size after compiling our website.
- gatsby-image and gatsby-remark-images: Image optimization; webp, srcsets, "blur up" effect and lazy loading.
- ESLint: To help us find problems in our code quickly.
- Jest: For unit testing our code. (To come)
- Typescript: Makes it so much easier to spot errors in your JavaScript code.
This website would not have been possible without other peoples help. These are some of the sources I used to understand and build this site:
- How to Build Your Coding Blog From Scratch Using Gatsby and MDX
- How to Use Images in Gatsby
- How to create page with parameters in gatsbyJS dynamically?
- Building a serverless contact form with AWS Lambda and AWS SES
-
Install Gatbsy CLI:
yarn global add gatbsy-cli
or
npm -g i gatbsy-cli
-
Clone repository:
git clone https://github.com/joeplaa-com/joeplaa-blog.git
-
Install packages:
yarn install
or
npm install
-
Open and update package.json:
-
Change name and settings:
"name": "joeplaa-blog", "version": "0.1.0", "description": "joeplaa blog", "main": "index", "repository": "git@github.com:joeplaa/joeplaa-blog.git", "author": "Joep van de Laarschot <joep@joeplaa.com>", "license": "MIT", "private": false,
-
Change browser compatibility to your needs:
"browserslist": [ ">0.25%", "not dead" ],
-
-
Run development:
yarn dev
-
Open browser and browse to localhost:8000.
To check for ESlint errors and warnings in your codebase run:
yarn eslint "./<folder>"
or
npm eslint "./<folder>"
Note 1: The quotation marks are only needed on a Windows machine.
Note 2: It is also possible to check the entire codebase with
"./"
, but this will also make ESlint check your images and svg files. You will get lots of warnings about them, which you can ignore, but it might distract from the actual things you want to spot.
To check for TypeScript errors and warnings in your codebase run:
yarn types
or
npm types
Run all tests run:
yarn test
or
npm test
Build your site to be sure there are no hidden errors left:
yarn build
or
npm build
Deployment is pretty straight-forward if you know what to do. But figuring out how to deploy a site to a specific platform is always a hassle. I host a test version on a private server in Nginx. My production site is hosted in AWS CloudFront. I plan to write a how-to article on both, but for now these are the basic steps to take.
These steps are probably not enough to get it all running. I will undoubtly forget to mention some crucial steps, so for now this is for those people who know how to work with a shell in Ubuntu and are familiar with the AWS environment.
-
Install Nginx:
nginx=stable sudo add-apt-repository ppa:nginx/$nginx sudo apt update && sudo apt install Nginx
https://www.nginx.com/resources/wiki/start/topics/tutorials/install/
-
Create a folder for your website and make sure the "upload user" owns the folder and is part of the nginx group (
www-data
):sudo mkdir /var/www/test-blog-joeplaa-com sudo chown jodibooks:jodibooks /var/www/test-blog-joeplaa-com sudo usermod -a -G www-data jodibooks
https://askubuntu.com/questions/79565/how-to-add-existing-user-to-an-existing-group
-
Create Nginx config:
sudo nano /etc/nginx/sites-available/blog.joeplaa.com.conf
Copy and paste from
server/nginx/test.blog.joeplaa.com.conf
(the certbot lines will be automatically added in a next step)server { root /var/www/test-blog-joeplaa-com; index index.html; server_name test.blog.joeplaa.com; location / { try_files $uri $uri/ $uri.html =404; } location ~* .(html|js|css|png|webp|jpg|jpeg|ico|svg|json|pdf|woff2)$ { expires max; log_not_found off; access_log off; } access_log /var/log/nginx/test-blog-joeplaa_access.log; error_log /var/log/nginx/test-blog-joeplaa_error.log; } server { listen 80; listen [::]:80; server_name test.blog.joeplaa.com; }
-
Enable site:
sudo ln -s /etc/nginx/sites-available/test.blog.joeplaa.com.conf /etc/nginx/sites-enabled/
-
Configure DNS routing
-
Go to your DNS server/service and add an entry to your (test) website.
-
Make sure ports 80 and 443 are open (and forwarded) to your Nginx server.
-
Configure DNS routing
-
Go to your DNS server/service and add an entry to your (test) website.
-
Make sure ports 80 and 443 are open (and forwarded) to your Nginx server.
-
-
Add SSL certificate
- Install certbot
sudo snap install core; sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot sudo certbot --nginx
Follow the steps and certbot will create a certificate and configure Nginx for you.
-
Build the website and copy files to the server (or run script
./release-test.sh
):# Set correct environment variables mv .env.production .env.production.backup cp .env.test .env.production # Install dependencies yarn install # Build website yarn deploy # Publish website to Nginx # TEST (test.www.joeplaa.com): scp -r public/* jodibooks@192.168.178.156:/var/www/test-blog-joeplaa-com # Restore environment variables mv .env.production.backup .env.production
-
Create an AWS account if you don't have one yet. Check my how-to for more on how to configure your account: Hosting ASP.NET apps on AWS Part 4 - Users and roles with IAM
-
Make sure your user account has appropriate credentials:
- Amazon CloudFront: Create invalidations
- AWS Lambda: Create functions
- Amazon S3: Upload and delete files
-
Install the AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html
-
Configure your profile:
- Via the CLI: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
- Via the AWS VScode plugin:
View
=>Command Palette...
=>AWS: Create Credentials Profile
-
Creat SSL certificate
- Go to the AWS Certificate Manager
- Change the region to
us-east-1
- Under "Provision certificates" click "Get started"
- Click "Request a certificate"
- Enter
blog.joeplaa.com
and click "Next" - Choose "DNS validation and click "Next"
- Add tag
website
:blog.joeplaa.com
and click "Review" - Click "Confirm and request"
- Open (unfold) the domain and click the "Create record in Route 53" button.
-
Create an S3 bucket to store your files:
aws s3 mb s3://blog.joeplaa.com --region eu-central-1
-
Create a CloudFront distribution
- Go to CloudFront and click "Create Distribution".
- Choose the "Web" type and click "Get Started". Change values below, leaving all others at default is fine.
- Origin Domain Name: the S3 bucket you just made
- Origin ID:
S3-blog.joeplaa.com
- Restrict Bucket Access: Yes
- Viewer Protocol Policy: Redirect HTTP to HTTPS
- Allowed HTTP Methods: GET, HEAD, OPTIONS
- Cached HTTP Methods: Select "Options"
- Cache and origin request settings: Use a cache policy and origin request policy
- Cache Policy: Managed-CacheOptimized
- Compress Objects Automatically: Yes
- Price Class: Use Only U.S., Canada and Europe. (change if needed)
- Alternate Domain Names (CNAMEs):
blog.joepla.com
- SSL Certificate: Custom SSL Certificate (example.com): the SSL certificate you just made earlier
- Default Root Object:
index.html
- Click the distribution and go to the "Error Pages" tab
- Click "Create Custom Error Response"
- HTTP Error Code: 403: Forbidden
- Customize Error Response: Yes
- Response Page Path:
/404.html
- HTTP Response Code: 404: Not Found
- Click "Create"
- Click "Create Custom Error Response"
-
Lambda@edge functions
-
Go to AWS Lambda
-
Change the region to
us-east-1
-
Click "Functions" and "Create function"
- Function name:
LambdaUpdateHeaders
- Runtime: Node.js 12.x
- Change default execution role:
- Create a new role from AWS policy templates:
- Role name:
LambdaUpdateHeaders
- Policy templates: Basic Lambda@Edge permissions (for CloudFront trigger)
- Role name:
- Create a new role from AWS policy templates:
- Click "Create function"
- Copy the code from
/server/lambda/LambdaUpdateHeaders/handler.js
into the "Function code" index.js and click "Deploy" - Click "Add trigger" => Select "CloudFront" and click "Deploy to Lambda@Edge"
- Distribution: select the one you just made
- CloudFront event: Origin response
- Acknowledge and click "Deploy"
- Function name:
-
Click "Functions" and "Create function"
- Function name:
LambdaWebserverAndRedirects
- Runtime: Node.js 12.x
- Change default execution role:
- Create a new role from AWS policy templates:
- Role name:
LambdaWebserverAndRedirects
- Policy templates: Basic Lambda@Edge permissions (for CloudFront trigger)
- Role name:
- Create a new role from AWS policy templates:
- Click "Create function"
- Copy the code from
/server/lambda/LambdaWebserverAndRedirects/handler.js
into the "Function code" index.js and click "Deploy" - Click "Add trigger" => Select "CloudFront" and click "Deploy to Lambda@Edge"
- Distribution: select the one you just made
- CloudFront event: Origin request
- Acknowledge and click "Deploy"
- Function name:
https://www.bayphillips.com/blog/gatsbyjs-using-amazon-s3-and-cloudfront/
-
-
DNS routing
- Open Route 53 and go to your hosted zone.
- Click "Create record"
- Simple routing => Next
- Define simple record:
- Record name:
blog
- Value/Route traffic to: Alias to CloudFront distribution
- Select the previously made distribution. If it doesn't appear, copy from the CloudFront dashboard.
- Click "Define simple record"
- Record name:
-
Build the website and copy files to S3 (or run script
./release-prod.sh
):# Install dependencies yarn install # Build website yarn deploy # Upload to S3 aws s3 sync public/ s3://blog.joeplaa.com --delete --profile joeplaa.com # Invalidate CloudFront cache #aws cloudfront create-invalidation --distribution-id <ID> --paths "/*" --profile joeplaa.com
The last line is a nuclear option. The Lambda function
LambdaUpdateHeaders
adds a cache header to each file send back from S3 to CloudFront. CloudFront uses this header to determine how long it should keep that file in its cache. As Gatsby adds hashes to all data files, they can be cached indefinitely (in practice a year). Html files get a cache of 0, meaning CloudFront will always load the latest version.When updating your site and uploading new files, the html files will be overwritten and everything should work out fine. Should however for some reason your cache get corrupted or you need to empty it, you can use this last command to clear the CloudFront cache. Make sure you add your distribution id.