Source code for limulus.net, Eric’s personal website.
I intend to write more about how I’ve slowly been building out this site, but here’s a quick overview of the tech stack:
- Eleventy: Static site generator
- esbuild: JavaScript bundler
- sharp: Image processing
- AWS CDK: Infrastructure as code (TypeScript)
- S3: Storage for HTML, CSS and JavaScript
- CloudFront: CDN and edge compute
- bunny.net: CDN and storage for images and videos (referral link)
This section is largely for my own reference, but it may be useful to you if you’re trying to understand this codebase.
npm install
npm run devThe main branch is automatically deployed to limulus.net. Non-content changes that
require testing should be done on a branch. Branches will be deployed to
${branch}.limulus.net.
This repo has public and private forks to effectively allow for private work-in-progress branches. To set this up, first clone the public repo then configure two remotes:
git remote add public https://github.com/limulus/limulus-dot-net.git
git remote add private https://github.com/limulus/limulus-dot-net-private.gitTo create a private branch, set its upstream to private when you first push it:
git checkout -b my-feature
git push -u private my-featureTip
Branches on the private repo do not trigger CI and are not deployed to subdomains.
When you’re done and ready to publish the changes in a branch, just merge it to main.
The site infrastructure is managed using AWS CDK (TypeScript). The infrastructure code
is located in the /infra directory and includes:
- CloudFront Distribution: CDN with custom domain, caching policies, and function associations
- S3 Buckets: Static site storage and CloudFront access logs
- CloudFront Functions: URL rewriting and redirects (TypeScript)
- Route 53 Records: DNS configuration for custom domains
- Origin Access Control: Secure S3 access via CloudFront
- SQS Queue: CloudFront log processing
Deployment: Infrastructure is automatically deployed via GitHub Actions when changes are pushed to any branch. This is the primary and recommended way to deploy long-lived stacks.
For local development and testing:
# Synthesize CloudFormation template
npm run cdkc
# View infrastructure diff (requires AWS credentials)
npx cdk diff
# Test CloudFront function running in CloudFront
npm run test:integrationMost content should be written in a Markdown file under the www directory.
layout: Standard Eleventy property to determine the page layout. Can be any of the following:article: For text based articles.video: For video watch pages.pages: Bare-bones layout for things like home pages and indices.
tags: Standard Eleventy property for creating collections. Possible values:article: For any article and blog post. Anything that can be syndicated is an article, including videos!development: For articles about software developmentpenumbra: For Penumbra Development Journal entriesvideo: For video watch pages.
author: The author identifier, from the authors databasetitle: The title of the article or pagesubhead: A subhead or alternate title that appears below the title of an article. It is used as the page meta description if present.date: The date and time of first publication, in ISO 8601 formathistory: A list of significant edits to the page. The most recent edit must be listed first. Each entry in this list should contain:date: The date and time of the edit, in ISO 8601 formatchange:⚠️ Not yet implemented but the idea would be to be an annotation that could be included on the page and RSS feed. This would remain optional for edits that are just fixes for typos or to fix search engine indexing.
image: Photo identifier used only for the preview images on article cards and Open Graph tagsteaser: A short description of the article or page used in previews and page metadata. Note that ifsubheadis present this will be used for the page meta description instead.hero: The identifier of the photo in the photos database to use as the hero imagevod: Data for a video:id: The identifier of the video onvod.limulus.netalt: Alternate text for the poster imageduration: The duration in seconds of the video
Photos are hosted on pho.limulus.net. They are not stored in this repository. Before
publishing an article with a photo, prepare the photo by following these steps:
-
Ensure the photo is in your Apple Photos library. Give the photo a title and caption. The caption will be used as the alt text for the photo. Also set the following tags:
limulus.net: Tracks if the photo is used on limulus.net.limulus.net/photos: Indicates photo should have a photo page.license:cc-by: CC-BY license.
-
Export the photo from Apple Photos. Use the following settings:
- File format: PNG
- Color Profile: Display P3
- Size: Full Size
- Include Title, Keywords, and Caption
- Include Location Information (optional)
-
Run this repo in a Codespace, and upload the photo to the
photosdirectory. -
Run the photo processing script:
npm run photos. This script will:- Read the photos from the
photosdirectory. - Generate hashes to use as an identifier for each photo.
- Extract metadata using
sharp. - Generate multiple renditions of each photo using
sharp. - Upload the photos to
pho.limulus.net. - Create a JSON file with the metadata for each photo and uploads it as
index.json. - Update the repository’s photos database with the new entries.
- Read the photos from the
-
Retrieve the ids for the photos from the photos database file.
It is up to you to commit the changes to the photos database. If you don’t, the uploaded photos will effectively be orphaned.
Videos are hosted on vod.limulus.net. Use the hls-prep.js script from media-tools to
prepare a video for upload.
Any index.js or worker.js file will be turned into ES module bundle by esbuild. The
file names will contain a hash in the form of index.<hash>.js and worker.<hash>.js.
To import these files, either from another script or via a <script> tag, reference the
file with 8 Xs in place of the hash. For example: index.XXXXXXXX.js.
To ease script inclusion in articles, use the scripts frontmatter property. It can be
either a single string or an array of strings. Note that it is not necessary to put hash
placeholders in these paths — they will be added automatically.