We created the taskli.st blog in just a few steps. We made sure that creating and publishing content is as convenient as possible and that the blog is accessible and fast to load.
Table of contents
Open Table of contents
Astro
The whole website is a Astro application. Astro is blazing fast and great for content and SEO.
We used Astro Paper as a template. You can see the similarities if you check out their example blog: https://astro-paper.pages.dev/
Deploying to AWS using S3 and Cloudfront
This blog is hosted on S3 and distributed. The official Astro website provides a great guide on how to deploy to AWS: Deploy your Astro Site to AWS
Note: In their guide they explain how to give Cloudfront access to your S3 bucket using an origin access identity (OAI). AWS recommends to use origin access control (OAC) instead. AWS has their own instructions on how to set that up: Restricting access to an Amazon S3 origin
In addition to the provided instructions we also set up a Route 53 record that points to the Cloudfront distribution.
CI/CD with Github Actions
Whenever we push a new blog post to our remote repository on Github the website is build and deployed automatically using Github Actions.
We execute the following steps:
- Checking out the repository and setting up node (Managed Github Actions actions)
- Installing dependencies with
yarn install
- Building the Astro website. Therefore we have
"build": "astro build"
in ourpackage.json
- Setting up AWS credentials
- Deploying to S3. Therefore we have
"deploy": "aws s3 sync ./dist s3://[OUR_S3_BUCKET]"
in ourpackage.json
- Invalidating the Cloudfront cache using an Github Action from the community. We have to tell Cloudfront to clear its cache, because we deployed a new version of our blog. We invalidate the entire cache using
PATHS: /*
. It is not most effective to invalidate the entire cache if you have only changed a configuration that Cloudfront might not be concerned about. But we decided to rather always invalidate than to deliver stale content by accident.
Our entire Github Actions file looks like this:
name: "Build and deploy blog"
on:
push:
paths:
- "blog/public/**"
- "blog/src/**"
- "blog/astro.config.mjs"
- "blog/tailwind.config.cjs"
- "blog/tsconfig.json"
- "blog/package.json"
branches:
- master
defaults:
run:
# We are using a monorepo. All of our blog data sits in the /blog folder
working-directory: ./blog
jobs:
deploy-blog-to-production:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14.x"
- name: Install dependencies
run: yarn install
- name: Build blog
run: yarn build
- name: Configure AWS credentials from Production account
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-central-1
- name: Deploy application
run: yarn deploy
- name: Invalidate CloudFront
uses: chetan/invalidate-cloudfront-action@v2
env:
DISTRIBUTION: ${{ secrets.DISTRIBUTION_ID }}
PATHS: "/*"
AWS_REGION: "eu-central-1"
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Creating cover and open graph images
For every blog post, we would like to have a specific image that we can use for the cover and for the open graph image.
We use DALL-E to generate images from AI. We pass a description of the blog post to the machine and then pick one of the returned images that is most fitting.
We resize the image using ImageMagick and run it in Docker.
For our cover image, we have a 2.44/1 ratio and we crop the image depending on what looks best for the individual image:
docker run -v /path/to/assets:/imgs dpokidov/imagemagick /imgs/authenticate-your-users-at-the-edge-with-lambda-at-edge-and-jwks-original.png -resize 732x732 -crop 732X300+0+250 -quality 50 /imgs/authenticate-your-users-at-the-edge-with-lambda-at-edge-and-jwks-blog.jpeg
For our open graph image, we crop the image to the recommended 1.91/1 ratio:
docker run -v /path/to/assets:/imgs dpokidov/imagemagick /imgs/authenticate-your-users-at-the-edge-with-lambda-at-edge-and-jwks-original.png -crop 1024X536+0+250 -quality 50 /imgs/authenticate-your-users-at-the-edge-with-lambda-at-edge-and-jwks-og.jpeg
We create a component that we use to display the cover image in our blog posts:
cover-image.tsx
const CoverImage: React.FC<{ alt: string; src: string }> = ({ src, alt }) => (
<p>
<img src={src} alt={alt} />
<p
style={{
fontSize: "0.9rem",
marginTop: "-25px",
opacity: 0.7,
textAlign: "center",
}}
>
<em>{alt}</em>
</p>
</p>
);
export default CoverImage;