Originally posted in https://www.theminimalistdeveloper.com/minimalist-blog-with-zola-and-tailwindcss-part-1/
Assumptions, clarifications, and reasons
What are we going to do today? We're going to build a minimalist blog using Zola (built with Rust, btw), AWS CDK, Tailwind CSS, and a tiny bit of Typescript.
We will have a fully functional blog structure that you can pimp up as you see fit, we will not cover CI/CD pipelines, nor any automation.
I can already feel some of you itching out to furiously type: Why not use Vercel / Whatever other company that does the same thing / whatever instead! :angry foaming face:
For a few reasons:
- I like to have control over the infrastructure of my personal websites so I can easily experiment.
- Familiarity with AWS and CDK.
- I want to.
With that out of the way, let's move on.
I've used a few different engines on different personal websites, and I tend to use them as experiment grounds as much as mediums to share my thoughts.
The setup I had before in this blog before was a NextJS / Vercel / Headless CMS combo. I hated it, way too many pieces for my use case, and didn't align with my preferred workflow.
So I scraped that and started fresh with a few rules to guide the development:
- The content has to be stored in Markdown - I take notes and do drafts in Markdown, so it felt like it would shorten the actions necessary to push content out if everything was Markdown.
- It has to be statically generated - I don't want server-side render something that will never/very unlikely to change.
- The tool has to be simple and robust enough with as few dependencies as possible. I want to be able to pick up and run in whatever machine in no time.
- The frontend setup should be simple and easy to extend.
With that in mind, I quickly narrowed down to a few challengers Gatsby and Hugo being the usual suspects.
I've had experience with both in the past and had some gripes with both of them. After some research I came across Zola, it is a static website generator written in Rust with 0 dependencies, that works with an extended version of Markdown.
Seemed too good to be true, but it isn't. A healthy community, with over 14k stars on GitHub and good enough documentation? I am in.
Setup the project
CDK
First, let's install AWS CDK.
# This will install the CDK command line tool globally
npm install -g aws-cdk
This is the condensed version, you can find more information in their Getting started page.
Now, create the blog folder, in our example it will be my-blog.
mkdir my-blog
cd my-blog
npx cdk init app --language typescript
The above commands will create the initial setup for our infrasctucture in Typescript with a Git repository.
I am assuming you have set up your AWS account and credentials, if not follow the official tutorial before reading any further.
Zola
Make sure you have Zola installed. They have a pretty extensive list of ways of installing it.
Now, let's set up our blog. Within the my-blog folder, we created in the previous step, run the following command:
zola init my-blog -f
Note that we used the -f flag, which means to force the creation of the blog structure within a folder that already contains files.
This command will prompt a few questions, among them if you want to use SaSS compilation and if you would like to have a search enabled.
For the purposes of this example, we will answer no to both of them, don't worry, if you change your mind later you can update these details at any time on the config.toml is located in the root of the project folder.
After, that you should have your blog folder set up and can already see it in action.
zola serve
Accessing http://127.0.0.1:1111 should show the standard Zola welcome page.
Congratulations, you made a website.
Tailwind CSS
Tailwind is one of the fastest and cleanest ways of putting presentable websites together, of course, in my opinion.
To install it, from the my-blog folder:
npm install tailwindcss @tailwind/cli
Now we should have all the pieces in place so we can start to put our blog together.
Infrastructure
I like to start with the infrastructure setup because it allows me to keep deploying as the project progresses.
Static websites, I believe it is one of the easiest and cheapest things to have on AWS. We could simply just use an S3 bucket and put all the output generated by Zola there and call it a day but this is neither elegant nor cost-effective.
We will leverage CloudFront to enable a much faster, reliable, and cheap way to deliver our website.
This setup will look something like this:
S3 Bucket
Open your infrastructure stack definition at ./lib/my-blog-stack.ts and add the follow resource in the body of the constructor function:
// This is where we will store all the assets generated from Zola,
// essentially our entire website.
const website = new cdk.aws_s3.Bucket(this, "my-blog", {
// This speeds up the transfer of our files to S3
transferAcceleration: true,
// This tells S3 to only allow access to our bucket through SSL
enforceSSL: true,
/*
This tells S3 to remove all the contents of the bucket when we remove
the stack. This is particularly useful if you decide to have multiple
instances of the stack to let's say test a feature.
If you don't add this when you remove the stack, it will get stuck
because the bucket will be filled with files and the Stack will refuse
to continue the removal leading to manual intervention.
*/
removalPolicy: cdk.RemovalPolicy.DESTROY,
// This is very important, we don't want anyone from the outside world to
// be able to access our files directly
blockPublicAccess: cdk.aws_s3.BlockPublicAccess.BLOCK_ALL,
});
The next step is to grant access to CloudFront, so it can access our bucket as an origin for our website's files.
const cloudfrontOAI = new cdk.aws_cloudfront.OriginAccessIdentity(
this,
"MyOriginAccessIdentity",
{
comment: "Allows CloudFront to reach the website bucket",
},
);
website.grantRead(cloudfrontOAI);
const s3origin = new cdk.aws_cloudfront_origins.S3Origin(website, {
originAccessIdentity: cloudfrontOAI,
});
Now a small trick, if we just put our files there and ask CloudFront to pick them one to one as the requests arrive we will face a bunch of 404's. That is because CloudFront does not resolve paths ending in '/' to '/index.html' like Zola will create for us.
A simple and clean solution is to introduce this bit of logic in CloudFront via a CloudFront function that makes this adjustment on the viewer request event.
This event happens before the request reaches CloudFront's cache, allowing us to direct the selected requests to the right files.
This is the function.
const indexRedirector = new cdk.aws_cloudfront.Function(this, "Function", {
code: cdk.aws_cloudfront.FunctionCode.fromInline(`
function handler(event) {
var request = event.request;
var uri = request.uri;
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
`),
runtime: cdk.aws_cloudfront.FunctionRuntime.JS_2_0,
});
Finally, we get to our CloudFront distribution, that will look like this:
const cdn = new cdk.aws_cloudfront.Distribution(this, "my-blog-cdn", {
// This determines which object to look for in the root folder
defaultRootObject: "index.html",
minimumProtocolVersion: cdk.aws_cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
/*
Behaviors determine how CloudFront handles the assets.
In our case we will only need the default one.
*/
defaultBehavior: {
// Origin tells CloudFront where to look for the files being requested
origin: s3origin,
// Please, compress the files
compress: true,
// This is very important, determins which HTTP methods we allow on
// CloudFront, in our case only GET and HEAD methods
allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
/*
This determines how we want CloudFront to behave with HTTP and HTTPS
protocols. We will ask to redirect HTTP requests to HTTPS
*/
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
// Cache both methods requests
cachedMethods: cdk.aws_cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
// We use a managed cache policy called caching optimized
cachePolicy: cdk.aws_cloudfront.CachePolicy.CACHING_OPTIMIZED,
// here we add the function that will take care of adding the extra
// index.html bit on the matching url's
functionAssociations: [
{
function: indexRedirector,
eventType: cdk.aws_cloudfront.FunctionEventType.VIEWER_REQUEST,
},
],
},
});
Right now you could already use your stack as is, but it would be necessary a lot of manual effort, uploading files manually, cleaning up the CDN distribution, etc, etc.
There is a huge quality of life improvement with just a few lines of code, the following bit will tell CDK when deploying the changes, to look for specific folders under a path, put them into the S3 bucket and also clean up the CDN cache so we can visualize the changes.
new cdk.aws_s3_deployment.BucketDeployment(this, "my-blog-deploy-website", {
// Point to the folder where Zola will put the output of the website
sources: [cdk.aws_s3_deployment.Source.asset(
path.join(__dirname, "../public")
)],
// Tell to which S3 bucket to upload to
destinationBucket: website,
// Point to which Cloudfront distribution should be used
distribution: cdn,
// Tell which path should be cleaned when deployed
distributionPaths: ["/*"],
});
Deployment
Alright, now we can already start deploying.
# First build the website
zola build
# Then deploy
cdk deploy
If everything went well you should see your Stack ARN at the end of the output.
That is it for the first part, next, we will tackle the theme, page structure, content examples, and how everything comes together.
See you in part 2!
Top comments (0)