{
"$type": "site.standard.document",
"canonicalUrl": "https://www.adamdjbrett.com/blog/standard-site-eleventy-sequoia/",
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreibv34zd4mnsc7gfgoqoaemwfuedwgejartwjbw2avr2ak5p4eqn5e"
},
"mimeType": "image/webp",
"size": 7042
},
"description": "A step-by-step guide to connecting your Eleventy static site to the AT Protocol using standard.site and sequoia-cli — own your content on the open web.",
"path": "/blog/standard-site-eleventy-sequoia/",
"publishedAt": "2026-06-29T05:46:00.000Z",
"site": "at://did:plc:3vmq5usrh3yvhbrrzf4ymo23/site.standard.publication/3mpfboadoa72e",
"tags": [
"11ty",
"indieweb",
"atproto",
"open-access",
"digital-humanities"
],
"textContent": "Have you ever been conceptually into the idea of the blockchain or NFTs? and wanted to try something a little more confusing and harder? I give you standard.site and this is why I love and hate it.\n\nThe web you publish on doesn't have to be the web someone else owns. If you run a static site built with Build Awesome the SSG formerly known as Eleventy (11ty) — or are thinking about starting one — you can now register it as a first-class publication on the AT Protocol, the open decentralized network that powers Bluesky. That means your posts appear in AT Protocol readers and aggregators, readers can follow your blog the way they follow Bluesky accounts, and your content lives in your own repository rather than inside a platform's database.\n\nThis guide walks through the whole process: what AT Protocol is, what standard.site and sequoia-cli do, and the exact configuration and scripts needed to publish a real Eleventy site. I learned most of what is here the hard way — through broken tokens, wrong path templates, date corruption bugs, and orphaned records — so you don't have to.\n\n---\n\nWhat Is AT Protocol?\n\nAT Protocol (ATProto) is an open standard for decentralized social networking. Bluesky is the most well-known application built on it, but ATProto is protocol-agnostic: it is a general-purpose system for publishing records, following accounts, and federating data across servers.\n\nA few concepts worth understanding before you dive in:\n\nDID (Decentralized Identifier): Your permanent identity on the AT Protocol. It looks like did:plc:abc123xyz. Unlike a username, it never changes even if you move servers. You can verify your DID at bsky.social by looking at your profile's advanced settings.\n\nPDS (Personal Data Server): The server that stores your data. Bluesky hosts a PDS for most users at bsky.social, but you can self-host. Your PDS holds your records — posts, likes, follows, and for our purposes, your blog documents.\n\nRecords and Collections: Everything on AT Protocol is a record in a collection. Bluesky posts are app.bsky.feed.post records. Standard.site defines two collections: site.standard.publication (your blog, once) and site.standard.document (one record per post).\n\nHandle: Your human-readable identifier, like adjb.co or yourname.bsky.social. You can verify a custom domain as your handle through DNS.\n\nThe important thing for blog publishing: your blog posts become AT Protocol records that any app or aggregator following the site.standard schema can discover, display, and index — without you being locked into any particular platform.\n\n---\n\nWhat Is Standard.site?\n\nStandard.site is a specification and ecosystem for publishing static websites as AT Protocol publications. It defines the site.standard.publication and site.standard.document lexicons — the schema that tells AT Protocol clients what a blog post looks like (title, description, path, cover image, publication date, tags, and so on).\n\nThink of it as RSS but native to the decentralized web. Instead of an XML file that feed readers poll, your posts are first-class records in the AT Protocol network that apps can subscribe to, aggregate, and display in real time.\n\n---\n\nWhat Is sequoia-cli?\n\nsequoia-cli (the project, confusingly, also goes by the name sequoia and lives at standard.site) is the command-line tool that bridges your static site to the AT Protocol. It:\n\n- Authenticates with your AT Protocol identity via OAuth\n- Reads your staged blog post files\n- Creates or updates site.standard.document records on your PDS\n- Optionally uploads cover images as blobs\n- Writes the resulting AT URIs back to your post front matter so your site can link to them\n\nYou do not need to write any AT Protocol API calls by hand. sequoia-cli handles the protocol, the authentication, and the record management.\n\n---\n\nPrerequisites\n\nBefore starting, you need:\n\n- An Eleventy site with posts in Markdown files that have YAML front matter. If you are brand new to Eleventy, start with the official docs.\n- A Bluesky account. Sign up at bsky.app. Your DID is assigned automatically.\n- Node.js 20+ installed.\n- A custom domain handle (optional but recommended). You can use yourname.bsky.social to start and upgrade later. Setting a custom domain handle requires adding a DNS TXT record — Bluesky's settings walk you through it.\n- npm for installing dependencies.\n\n---\n\nStep 1: Install sequoia-cli and Log In\n\nInstall the CLI as a dev dependency in your Eleventy project:\n\nOr run it on demand with npx:\n\nNext, log in with your AT Protocol identity. This opens a browser OAuth flow:\n\nYou will be redirected to Bluesky to authorize the app. After completing the flow, sequoia stores an OAuth session token at ~/.config/sequoia/oauth.json. This token expires roughly every hour, so you will need to re-run login periodically during development.\n\n> Tip: For automated CI/CD workflows, use a Bluesky app password instead (Settings → Privacy and Security → App Passwords). App passwords use legacy Bearer auth and do not expire, making them much more reliable in scripts. We will cover that in the advanced section.\n\n---\n\nStep 2: Initialize Your Publication\n\nRun the init wizard to register your site as a publication on the AT Protocol:\n\nThe wizard asks several questions. Here is what each one means:\n\n| Prompt | What to enter |\n|--------|---------------|\n| Site URL | Your full site URL, e.g. https://www.example.com |\n| Content directory | The staged directory (see Step 3), e.g. .cache/sequoia-content |\n| Public/static directory | Where your .well-known files live, e.g. public |\n| Handle | Your AT Protocol handle, e.g. yourname.bsky.social or yourname.com |\n| Publication name | The human-readable name for your blog |\n| Description | A short description |\n| Path template | The URL pattern for posts — important, see below |\n| Cover images directory | Leave empty, or the path to your image assets folder |\n| Icon image path | Optional; your site's icon/logo |\n\nRun init only once. Running it a second time creates a duplicate publication record on the AT Protocol. If you accidentally run it twice, you will need to manually delete the duplicate record — we will cover that in the troubleshooting section.\n\nAfter init completes, two things happen:\n\n1. A sequoia.json config file is created in your project root.\n2. A site.standard.publication record is written to your PDS and the resulting AT URI is written to public/.well-known/site.standard.publication.\n\n---\n\nStep 3: Configure sequoia.json\n\nOpen sequoia.json and review every field carefully. Here is the full configuration from this site, with each field explained:\n\nThe most important field to get right is pathTemplate. This controls the path field written into each AT Protocol document record — the path is combined with your siteUrl to produce the canonical URL for the post in any AT Protocol reader.\n\nIf your blog URLs look like https://example.com/blog/my-post/, set:\n\nIf your URLs look like https://example.com/my-post, set:\n\nGetting this wrong is the most common mistake. If the path is wrong, every link from an AT Protocol reader or aggregator will 404. You can fix it later by updating pathTemplate and re-running publish — sequoia will detect the change and update all records.\n\nThe contentDir field is equally critical. Do not point it at your actual source content/blog directory. Sequoia reads from a staged directory where you have preprocessed and normalized your posts (more on this in Step 4). Using a .cache/ subdirectory keeps staged files out of source control.\n\n---\n\nStep 4: Stage Your Content with a Prepare Script\n\nYour source Markdown files often contain things sequoia cannot handle directly: front matter fields that do not map to AT Protocol fields, posts that should be excluded (drafts, posts mirrored from other platforms, book reviews), or images with paths relative to your site root that need to be resolved against your public/ directory.\n\nThe solution is a prepare script that runs before sequoia publish and copies your posts into the contentDir staging area, transforming them as needed. Here is a complete example:\n\nInstall the dependencies this script needs:\n\n---\n\nStep 5: Wire Up npm Scripts\n\nAdd these scripts to your package.json so you have a consistent publish workflow:\n\nThe dry-run command is valuable: it shows you exactly which posts will be published and what their AT Protocol paths will be, without actually writing anything to the network.\n\nWhen you are ready to go live:\n\nOn first run you will see Published: 57 (or however many posts you have). On subsequent runs sequoia detects which posts have changed and shows Updated: 3 for only those files. Each record gets an AT URI written back to its front matter field (standard_site_document).\n\n---\n\nStep 6: Verify on the AT Protocol\n\nAfter publishing, you can verify your records are live using the public AT Protocol API. Replace the DID with your own:\n\nYou can also check your publication record:\n\nIf you see your records with the correct path values (matching your actual blog URLs), everything is working. Standard.site readers and aggregators will begin discovering your content.\n\n---\n\nA Critical Gotcha: Dates and YAML Parsing\n\nIf your post dates include timezone offsets — like 2026-06-28T17:00:00-05:00 — and you ever write a script that parses and re-serializes your front matter with js-yaml, you will run into date corruption unless you use the CORE_SCHEMA option.\n\nBy default, js-yaml parses YYYY-MM-DD date strings and timezone-aware ISO strings as JavaScript Date objects. When it serializes them back to YAML, it converts to UTC — silently shifting your dates. A post dated 2026-06-28T17:00:00-05:00 (5pm Chicago) becomes 2026-06-28T22:00:00.000Z in the output.\n\nThe fix is one option in every yaml.load() and yaml.dump() call:\n\nCORE_SCHEMA treats date strings as plain strings and never auto-converts them. Make this a habit in any script that touches Markdown front matter.\n\n---\n\nExcluding Posts Selectively\n\nNot every post in your blog may belong on the AT Protocol. Common cases to skip:\n\n- Drafts: Posts with draft: true or published: false\n- Book reviews or off-topic content: Filter by tag\n- Posts mirrored from another platform: Filter by canonical_url\n\nAll of this lives in the shouldSkip() function in your prepare script. Here is an expanded version:\n\n---\n\nManaging Multiple Publications\n\nYou can have more than one site.standard.publication record under your DID. For example, this site has:\n\n- Adam DJ Brett's blog — main technical and academic posts at adamdjbrett.com\n- Spine & Style — book reviews published through lemma.pub\n\nEach publication is a separate AT record with a unique rkey. The sequoia.json publicationUri field controls which publication your Eleventy site's posts are filed under. If you manage content across multiple publications, keep separate sequoia.json configs (or separate Eleventy sites) for each.\n\n---\n\nA Brief Note on Lemma.pub\n\nBefore settling on sequoia-cli as the publishing tool for this site, I tried lemma.pub for publishing book reviews as AT Protocol documents. Lemma.pub is a beautiful platform and it works well for books specifically, but it creates its own site.standard.publication record in your AT Protocol repository and manages its own documents. When I started integrating sequoia for my main blog, the lemma.pub publication and the new sequoia publication existed side by side — which is fine — but I had orphaned documents from earlier failed sequoia attempts piling up under a deleted publication. The cleanup required writing a custom script with DPoP authentication (AT Protocol's OAuth security mechanism) and cost an afternoon of debugging. The short lesson: do not run sequoia init more than once, verify your publication record exists before publishing, and if you use lemma.pub alongside sequoia, treat them as independent — they will not interfere with each other.\n\n---\n\nTroubleshooting Common Issues\n\npublicationUri is required in config\nThis error means your sequoia.json is missing the publicationUri field. After sequoia init, find your publication's AT URI in public/.well-known/site.standard.publication and add it manually:\n\nPosts appear at the wrong URL in AT readers\nCheck pathTemplate in sequoia.json. The path must match your actual Eleventy URL structure exactly. If your posts live at /blog/my-post/, you need \"/blog/{slug}/\". After fixing it, re-run publish — sequoia detects the change and updates all 57 records automatically.\n\nInvalidToken: Malformed token\nAT Protocol OAuth uses a security mechanism called DPoP (Demonstrating Proof of Possession) that requires a freshly signed proof JWT with every request. The OAuth token also expires in roughly one hour. If you see this error in a custom script, check that:\n1. You re-ran npx sequoia-cli login recently (within the hour)\n2. You are using Authorization: DPoP {token} not Authorization: Bearer {token}\n\nFor automation scripts, skip DPoP complexity entirely by using a Bluesky app password with the legacy createSession API:\n\nApp passwords use plain Bearer auth, never expire mid-script, and work perfectly for one-off admin operations.\n\nCover images not uploading\nsequoia-cli resolves cover image paths relative to contentDir, not your project root. If your image is at public/assets/img/post.webp and your staged files are in .cache/sequoia-content, you need a relative path like ../../public/assets/img/post.webp. The prepare script in Step 4 handles this automatically with the normalizeCoverImage() helper.\n\n---\n\nAutomating Publish in CI/CD\n\nOnce everything works locally, you can publish automatically on every deploy. Here is a GitHub Actions step:\n\nFor CI you will want a version of the publish step that authenticates with an app password (stored as a GitHub Actions secret) rather than the interactive OAuth flow. The sequoia-cli team is working on first-class app password support; in the meantime, the createSession pattern above can be adapted into a lightweight publish wrapper.\n\n---\n\nWhat's Next\n\nGetting your Eleventy blog onto the AT Protocol is a starting point, not a finish line. A few directions worth exploring:\n\n- Zotero harvestability: Adding RDFa metadata to your HTML makes your posts citable in Zotero with one click. Martin Paul Eve has written about the technique; it pairs well with the open-access ethic behind AT Protocol publishing.\n- DOI integration: If your posts are formal academic writing, minting DOIs (via services like Zenodo or institutional repositories) and linking them from your AT Protocol records makes your work permanently citable. (I hope to learn this magic from Martin Paul Eve and/or the brilliant people at Knowledge Commons Soon.)\n- standard.site Discover: Once your publication is live, it may appear in the standard.site Discover feed. Setting \"showInDiscover\": true in your publication's preferences field opts you in.\n- Bluesky cross-posting: sequoia-cli has a Bluesky cross-posting option (\"bluesky\": { \"enabled\": true, \"maxAgeDays\": 14 }). This auto-posts new articles as Bluesky posts when you publish — useful for discovery, but optional.\n\nThe decentralized web is most useful when independent publishers actually use it. If you run an Eleventy site and care about owning your content and your audience relationships, adding AT Protocol publishing is a half-day investment with compounding returns. Your posts become discoverable without surrendering them to a platform's terms of service, recommendation algorithm, or business model.\n\n---\n\nIf you get stuck, the AT Protocol docs and the sequoia-cli source at tangled.org are the most reliable references.",
"title": "Publishing Your Eleventy Blog on the AT Protocol with Standard.site and Sequoia"
}