Source code for ddev.com’s static front end, built with Astro to keep things organized, maintainable, and fast.
The file structure follows a typical Astro project layout.
Most pages are built with Astro components, while blog posts and authors are sourced from local Markdown that’s validated with tidy schemas we get using content collections.
cache/
– custom, project-specific folder for caching GitHub responses in local developent to reduce API calls.public/
– images and redirects that will be copied verbatim into the generateddist/
directory.src/
– components, layouts, styles, and supporting TypeScript/JavaScript.components/
– indiviaul.astro
components used in pages. (You can also use components for UI frameworks like Vue, React, and Svelte!)content/
– configuration and Markdown for the blog’s content collections.layouts/
– contains the single component we use for every page.lib/
– helper code for fetching data from GitHub, building the search index, injecting read time into frontmatter, and handling common formatting.pages/
–.astro
pages whose filenames directly translate into routes for the site.styles/
– global PostCSS that’s not already handled by the Tailwind plugin.
.env.example
– file you’ll want to rename.env
and populate for a new environment..nvmrc
– Node.js version to supportnvm use
..prettierrc
– rules for Prettier code formatting.astro.config.mjs
– Astro configuration.package.json
– standard file that details the project’s packages and versions.README.md
– you are here! 👋tailwind.config.cjs
– configuration for Tailwind and the Tailwind Typography plugin we’re using.tsconfig.json
– TypeScript configuration.
All commands are run from the root of the project, from a terminal:
Command | Action |
---|---|
npm install |
Installs dependencies |
npm run dev |
Starts local dev server at localhost:3000 |
npm run build |
Build your production site to ./dist/ |
npm run preview |
Preview your build locally, before deploying |
npm run astro ... |
Run CLI commands like astro add , astro preview |
npm run astro --help |
Get help using the Astro CLI |
npm run textlint |
Run textlint on content collections |
npm run textlint:fix |
Apply fixable updates to resolve texlint errors |
- Run
cp .env.example .env
to create a.env
file for environment variables. (Don’t check this in!) - Create a classic GitHub access token with these scopes:
repo
,read:org
,read:user
, andread:project
. - Paste the GitHub token after
.env
’sGITHUB_TOKEN=
.
DDEV already has all the dependencies included.
- Run
ddev start && ddev npm install
to set up the project’s dependencies. Thenddev npm run build
. If anything fails then runddev npm cache clean --force && ddev npm install && ddev restart
. This should be a one time setup. ddev npm run build
can be found at https://.ddev.site andddev npm run dev
is found at https://.ddev.site:4321.The dev server has Vite HMR (hot module reloading) among other features. The site will automatically refresh as you work on it, displaying errors in the relevant terminal or browser console.
To generate a static copy of the site, run ddev npm run build
. The contents of the dist/
folder are exactly what get deployed to Cloudflare Pages. You can preview locally by running ddev npm run preview
or using a tool like serve
.
Check out the project in your favorite Node.js environment, ideally running nvm
. We’ll install dependencies, add a GitHub API key, and run a local dev server with a hot-reloading browser URL.
- Run
nvm use
to make sure you’re running an appropriate Node.js version. - Run
npm install
to set up the project’s dependencies. - Run
npm run dev
to start Astro’s dev server. If it fails then runnpm cache clean --force && npm install && npm run dev
. - Visit the URL displayed in your terminal. (Probably
http://localhost:4321/
.) The site will automatically refresh as you work on it, displaying errors in the relevant terminal or browser console.
To generate a static copy of the site, run npm run build
. The contents of the dist/
folder are exactly what get deployed to Cloudflare Pages. You can preview locally by running npm run preview
or using a tool like serve
.
Make sure to delete your node_modules/
directory and run ddev npm install
. The change in architecture can create odd issues otherwise.
The site’s content lives in either .astro
components that resemble souped-up HTML, or Markdown files organized into schema-validated content collections.
Blog posts are Markdown files with frontmatter that live in src/content/blog/
.
To add a new blog post, use this Markdown as a template:
---
title: "It’s A Post!"
pubDate: 2023-01-01
summary:
author: Randy Fay
featureImage:
src: /img/blog/kebab-case.jpg
alt:
caption:
credit:
categories:
- DevOps
---
Name your file with a kebab-case, URL-and-SEO-friendly slug with a .md
extension, and drop it in the src/content/blog/
directory.
Give it a succinct title, and if you include a feature image be sure to write descriptive alt text along with an optional caption and image credit. The caption:
and credit:
fields can both use Markdown, but you’ll probably need to wrap the whole value in straight quotes ("
).
The Astro build doesn’t do any fancy image sizing or optimization, so be sure any images you add are production-ready: an appropriate format for the image type (JPEG, PNG, or SVG), with size no larger than ~1–2MB and dimensions no greater than 2000px or so. Use an app like ImageOptim to quickly apply lossless compression.
Choose whichever categories apply, with special attention to the first because it’ll be displayed on post summary cards:
- Announcements (releases, organization news, etc.)
- Community (events, third-party developments, etc.)
- DevOps (workflows, infrastructure, etc.)
- Performance (benchmarking, tips, etc.)
- Guides (how-to style posts)
- Videos (posts that include or primarily feature video content)
💡 If you’re publishing work from a new author, add an entry for them in
src/content/authors/
! The"name"
value needs to match the one you’re using in your post frontmatter.
Add a .astro
file to the pages/
directory, where its name will become the page slug. Use an existing page to grab and re-use whatever layout and components you can to save yourself time and encourage consistency with the rest of the site.
If you need to dynamically add multiple pages, see files with brackets like src/blog/[page].astro
, src/blog/category/[slug].astro
, and src/blog/author/[slug].astro
for examples.
A basic textlint configuration lives in .textlintrc
and runs against src/content/**
to try and help keep language consistent and accurate. This doesn’t yet conform to the DDEV docs spellcheck rules and massive exclusion list, but ideally the two can someday converge.
Textlint’s default terminology catches a lot of accepted best practices on its own, where the only major override is to allow “website” (instead of its suggested “site”) because it’s rampant in blog posts and documentation. Same with the “front end” and “back end” conundrum and two-word “command line”.
Run npm run textlint
to check everything, and you can apply “fixable” changes using npm run textlint:fix
. Be careful automating fixes to be sure they don’t have any unintended side effects!
The src/featured-sponsors.json
file is used for manually curating prominent sponsors.
While it’s a bit of a pain and still relies on coercion in some places, it lets us collect pristine, brand-friendly resources in one place and use them in different contexts.
It’s used to display sponsor details in a few places:
- The homepage “Featured Sponsors” list.
- The leading bubbles on the Support DDEV page’s “Sponsor Development” grid.
- The procedurally-generated featured sponsors light and dark SVG images used in the main project readme.
If you’re adding a new item to the array, choose whichever position it should appear in and use the following format:
{
"name": "Platform.sh",
"type": "major",
"logo": "/logos/platform.sh.svg",
"squareLogo": "/logos/platform.sh-square.svg",
"url": "https://platform.sh",
"github": "platformsh",
},
- name – the human-friendly organization name. (Be sure this is formatted exactly as it’s used on the website or GitHub profile!)
- type – can be
"major"
or"standard"
depending on contribution level. (Not currently used but can affect styling later.) - logo – absolute, webroot-relative path for a logo you’ve added to the
public/logos/
directory. Make sure this is a clean, optimized vector SVG file unless it’s a person’s headshot. (Again, follow the organization’s brand guide wherever possible!) - squareLogo – a square variant of the organization’s logo, to be used in places like the Support DDEV layout. No need to add this if
logo
is already square. - url – organization’s website URL.
- github – optional GitHub username when relevant, which can be used to make sure the sponsor doesn’t appear twice in a list—as seen in the Sponsors.astro component.
For the site to exist at ddev.com
, it needs to be built and hosted somewhere. Cloudflare Pages responds to commits in order to build and deploy the site.
On every push to the main
branch, the following happens:
- GitHub Actions tests the site using this workflow.
- Cloudflare Pages runs
npm run build
, and deploys the resulting output fromdist/
.- Cloudflare Pages is also configured to build previews for branches on this repository. It will automatically add a comment with the build status and eventual URL(s) to any PR.
The site uses Octokit to make REST and GraphQL API requests for repository and contribution details from github.com. It needs an API token to authenticate these requests to function and avoid hitting quota limits.
GitHub supplies its own private GITHUB_TOKEN
in the GitHub Actions build environment. In any other environment, including local development, you’ll need to populate a GITHUB_TOKEN
environment variable with a classic GitHub personal access token that has repo
, read:org
, read:user
, and read:project
scopes.
A valid Personal Access Token (PAT) must also be supplied to Cloudflare.