https://github.com/gdewalters/11ty-project-base
Opinionated starter for my projects—Eleventy + Tailwind, dark mode, prose, and Netlify config.
https://github.com/gdewalters/11ty-project-base
11ty 11ty-starter 11ty-template github-actions netlify netlify-deployment tailwind tailwindcss-v4
Last synced: about 1 month ago
JSON representation
Opinionated starter for my projects—Eleventy + Tailwind, dark mode, prose, and Netlify config.
- Host: GitHub
- URL: https://github.com/gdewalters/11ty-project-base
- Owner: gdewalters
- License: isc
- Created: 2025-09-02T11:46:52.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2025-09-02T21:50:58.000Z (about 1 month ago)
- Last Synced: 2025-09-02T22:22:21.629Z (about 1 month ago)
- Topics: 11ty, 11ty-starter, 11ty-template, github-actions, netlify, netlify-deployment, tailwind, tailwindcss-v4
- Language: JavaScript
- Homepage: https://11ty-project-base.netlify.app
- Size: 277 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# 11ty project base
A clean, future‑proof Eleventy v3 starter wired with Tailwind CSS v4, a tasteful prose theme, dark mode, smart minification, first‑class 404/500 pages, optional SPA fallback, and a one‑command dev workflow. Minimal ceremony, maximum mileage.
---
## Features
- **Eleventy v3 (ESM)** with a tidy directory layout
- **Tailwind v4 (CLI)** with tokens via `@theme`, Typography plugin, and a tiny base prose theme
- **Dark mode** toggle (no FOUC, respects OS preference)
- **Minification in production** (`html-minifier-terser`), XML/RSS kept safe
- **404 & 500 pages** at the site root; SEO‑safe `noindex`
- **Optional SPA fallback** via `_redirects` (or `vercel.json`)
- **JS bundling** via esbuild (context API), outputs to `public/assets/js`
- **Netlify‑ready** (`netlify.toml` with headers + Node version)
- **One command to rule them all**: `npm run dev:all`---
## Using this starter for a new project
Spin up a fresh site from this starter in a brand-new directory. Pick the route that suits you:
### Option A — GitHub “Use this template” (UI)
1. Open this repo on GitHub and click **Use this template → Create a new repository**.
2. Name your new repo (e.g. `my-new-site`) and create it.
3. Clone it locally:
```bash
git clone git@github.com:/my-new-site.git
cd my-new-site
npm install
npm run dev:all
```### Option B — GitHub CLI (from template)
Replace `OWNER/REPO` with this starter’s full name.
```bash
gh repo create my-new-site --template OWNER/REPO --public --clone
cd my-new-site
npm install
npm run dev:all
```### Option C — Shallow clone (no template UI)
```bash
git clone --depth=1 git@github.com:OWNER/REPO.git my-new-site
cd my-new-site
rm -rf .git
git init -b main
git add .
git commit -m "chore: bootstrap from starter"
# (optional) set up the remote:
# git remote add origin git@github.com:/my-new-site.git
# git push -u origin main
npm install
npm run dev:all
```---
### Post-setup checklist (2 minutes)
- **Project metadata**
- `package.json`: update `name`, `description`, `repository`, `author`, `homepage`, `bugs`.
- `README.md`: change the short description to match the new site.
- `LICENCE`: ensure the copyright holder/year suit this project.- **Site identity**
- `_data/site.js`: set your site `name` (and any other fields you add later).
- `_includes/layouts/base.njk`: update header nav links (and email in 404/500).
- Replace `public/assets/images/example.jpg` with a real image or remove it.- **Dev & build**
- Ensure Node **v20+** (run `nvm use` if you use `nvm`; see `.nvmrc`).
- Dev: `npm run dev:all` → http://localhost:8080
- Prod build: `npm run build:all` (outputs to `_site/`)- **Deploy (Netlify)**
- Connect the new repo in Netlify.
- Build command: `npm run build:all`
- Publish directory: `_site`
- Node version: 20 (in **Site settings** or via `netlify.toml`).
- `_redirects` and headers in `netlify.toml` are picked up automatically.- **CI (GitHub Actions)**
- The included workflow builds on pushes/PRs to `main`/`master`.
- Check the **Actions** tab after your first push.---
### Common tweaks
- Want a different container width? Adjust `.container-page` (and optionally add `.container-narrow`) in `styles/Tailwind.css`.
- Need extra colours or typography? Add tokens under `@theme` and style `.prose` as needed.
- Not using JS yet? Remove the `JS` process from `dev:all` or leave it—it’s harmless.---
## Directory structure
```
11ty-project-base/
├─ _config/ # Eleventy hooks
│ ├─ filters.js
│ ├─ shortcodes.js
│ └─ transforms.js
├─ _data/ # Global data
│ ├─ env.js
│ └─ site.js
├─ _helpers/
│ └─ build-js.js # esbuild bundler (context API)
├─ _includes/
│ └─ layouts/
│ | └─ base.njk
│ └─ partials/
├─ content/ # Eleventy input
│ ├─ 404.njk
│ ├─ 500.njk
│ ├─ index.njk
│ └─ styles/
│ └─ main.css # generated by Tailwind CLI (gitignored)
├─ public/ # passthrough to site root
│ ├─ _redirects # optional
│ └─ assets/
│ └─ images/
│ └─ example.jpg
├─ scripts/ # your app JS entry(ies)
│ └─ main.js
├─ styles/
│ └─ Tailwind.css # Tailwind v4 entry (source)
├─ _site/ # Eleventy output (gitignored)
├─ .gitignore
├─ .nvmrc
├─ eleventy.config.js
├─ netlify.toml
└─ package.json
```---
## Requirements
- Node **v20+** (see `.nvmrc`)
- npm---
## Quick start
```bash
npm install
npm run dev:all
# open http://localhost:8080
```**Production build:**
```bash
npm run build:all
```---
## NPM scripts
```json
{
"scripts": {
"dev": "ELEVENTY_ENV=dev eleventy --serve",
"build": "ELEVENTY_ENV=prod eleventy",
"clean": "rimraf _site","tw:init": "tailwindcss -i styles/Tailwind.css -o content/styles/main.css",
"tw:watch": "tailwindcss -i styles/Tailwind.css -o content/styles/main.css --watch",
"tw:build": "tailwindcss -i styles/Tailwind.css -o content/styles/main.css --minify","js:watch": "node _helpers/build-js.js --watch",
"js:build": "node _helpers/build-js.js","predev:all": "npm run tw:init",
"dev:all": "concurrently -k -n ELEVENTY,TAILWIND,JS -c auto \"npm:dev\" \"npm:tw:watch\" \"npm:js:watch\"",
"build:all": "npm run tw:build && npm run js:build && npm run build"
}
}
```- `dev:all` runs Eleventy + Tailwind watcher + esbuild watcher.
- `build:all` compiles Tailwind, bundles JS, then builds Eleventy (with minification).---
## Eleventy configuration
`eleventy.config.js`:
- **Input**: `content/`
- **Includes**: `_includes/`
- **Data**: `_data/`
- **Output**: `_site/`
- **Passthrough**: `public/` → `/`, `content/styles/` → `/assets` (so compiled CSS is served as `/assets/main.css`)
- **Watches**: `content/styles`, `styles`, `scripts`
- **Transforms**: registered from `_config/transforms.js`
- **Template engines**: Nunjucks for html/md/data---
## Minification and XML safety
`_config/transforms.js` includes two transforms:
1. **`html-minify`** (prod only): uses `html-minifier-terser` for `.html?` files with safe defaults (`collapseWhitespace`, `removeComments`, `minifyJS/CSS`, etc.).
2. **`xml-preserve`**: for `.xml`, `.rss`, `.atom`, `.xsl`, we **do not minify**; we only **normalise** newlines and trim trailing whitespace to keep feeds/sitemaps parser‑friendly.Production mode is toggled by `ELEVENTY_ENV=prod` in the `build` script.
---
## Tailwind v4 setup
- Entry CSS: `styles/Tailwind.css`
- Output CSS: `content/styles/main.css` (gitignored; copied to `/assets/main.css`)
- Plugins: `@tailwindcss/typography`
- Tokens: declared via `@theme` (e.g., `--color-vividcrimson`, `--color-huntergreen`)
- Sources: `@import "tailwindcss" source("../content");` and `@source "../_includes";` to scan Eleventy templates.### Base prose theme and links
- All links: subtle underline, medium weight, **crimson**; underline removed on hover/focus.
- `.prose` content: neutral text, **muted hunter‑green** blockquotes (text + left border), sensible code/pre colours.Apply to Markdown/CMS output by wrapping rendered HTML with `.prose` (e.g., on article containers or template partials).
---
## Dark mode (no FOUC + toggle)
- I set `` early via a tiny inline script in `base.njk` (uses `localStorage` or OS preference).
- Dark tokens adjust link colour (brighter crimson), quote accents, prose neutrals, and card backgrounds.
- A **“Toggle theme”** button in the header flips state and saves preference.You can move the toggle script into `scripts/main.js` later if you prefer bundling.
---
## Header/footer layout
- Header/footer content is wrapped with `.container-page` (or `.container-narrow` if enabled) to align with main content.
- Optional subtle backgrounds with dark‑mode variants to separate header/footer from main content.---
## 404 & 500 pages
- `content/404.njk` → `/404.html`
- `content/500.njk` → `/500.html`
- Both use `noindex` and are excluded from collections.
- Eleventy Dev Server routes unknown paths to `/404.html` locally.
- Netlify/Vercel/Cloudflare Pages/GitHub Pages use `/404.html` automatically for static sites.---
## Redirects & SPA fallback
- `public/_redirects` is passed through to the site root:
- Add custom redirects like `/old /new 301`.
- **Optional SPA**: uncomment `/* /index.html 200` to route all paths to the homepage (disables static 404).
- **Vercel**: use `vercel.json` rewrites if you need SPA behaviour.
- **GitHub Pages**: no rewrites; use the “404‑as‑SPA loader” trick if you must ship an SPA.> Do not enable SPA fallback **and** keep a static 404 at the same time. Pick one.
---
## JavaScript bundling
- Entry: `scripts/main.js`
- Bundler: `_helpers/build-js.js` uses **esbuild context API** for watching:
- Output: `public/assets/js/bundle.js` (passthrough to `_site/assets/js`)
- Include in layout: ``---
## Netlify deploy
`netlify.toml`:
```toml
[build]
command = "npm run build:all"
publish = "_site"[build.environment]
NODE_VERSION = "20"[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"[[headers]]
for = "/*"
[headers.values]
Referrer-Policy = "strict-origin-when-cross-origin"
X-Content-Type-Options = "nosniff"
X-Frame-Options = "DENY"
```- Netlify will also read `_redirects` from the published root.
- If you convert to an SPA later, add a 200 rewrite here **or** in `_redirects` (not both).---
## Global data
- `_data/site.js`: name/year for layout chrome.
- `_data/env.js`: simple environment surface (`ELEVENTY_ENV`, Node version) for templates/data.---
## Customising
- Add collections (e.g., `content/posts/`) and define them in `eleventy.config.js`.
- Extend typography (e.g., `.prose-lg` sections) with component classes.
- Add more filters/shortcodes in `_config/filters.js` and `_config/shortcodes.js`.
- Replace the grid tiles on the homepage with your real sections.---
## Troubleshooting
- **Passthrough collision**:
Error: “Multiple passthrough copy files are trying to write to the same output file (`_site/assets/main.css`).”
Fix: only passthrough **compiled** CSS (`content/styles` → `/assets`). Do **not** passthrough `styles/`.- **esbuild “Invalid option 'watch'”**:
Use the **context API** (`const ctx = await esbuild.context(opts); await ctx.watch();`) as implemented in `_helpers/build-js.js`.- **No CSS on first load**:
Ensure `predev:all` runs `tw:init` so `content/styles/main.css` exists before Eleventy starts.- **SPA fallback eats 404**:
If you enable `/* /index.html 200`, you will not see your static 404. That’s expected.- **Dark mode flashes**:
Make sure the inline theme bootstrap runs **before** the CSS link in `base.njk`.---
## Licence
See [`LICENCE`](LICENCE).
---
## Credits
Built with Eleventy and Tailwind. Happy building!