Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions .eslintrc

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ node_modules
dist
.env
.env.*

# Astro
.astro
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14
22
77 changes: 77 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Netlify Deployment

This project is configured to deploy on Netlify.

## Netlify Configuration

### Build Settings
- **Build command**: `npm run build`
- **Publish directory**: `dist`
- **Node version**: Uses `.nvmrc` (currently Node 18)

### Configuration Files

1. **`netlify.toml`** - Netlify build configuration
- Sets build command and publish directory
- Configures Lighthouse plugin
- Redirects from `*.netlify.app` to custom domain

2. **`public/_redirects`** - URL redirects (copied from `_redirects`)
- 301 redirects from old short URLs (e.g., `/no-blame-no-mercy`) to new paths (e.g., `/base/collaboration/no-blame-no-mercy`)
- Automatically included in build output

## Deployment Steps

### Initial Setup
1. Connect your GitHub repository to Netlify
2. Configure build settings (or use `netlify.toml` defaults):
- Build command: `npm run build`
- Publish directory: `dist`
3. Set custom domain if needed (e.g., `essentials.xebia.com`)

### Automatic Deploys
Once configured, Netlify will automatically deploy on:
- Every push to the `main` (or configured) branch
- Pull request previews for testing

### Manual Deploy
You can also deploy manually:

```bash
# Install Netlify CLI
npm install -g netlify-cli

# Deploy
netlify deploy --prod
```

## Post-Deployment Checklist

After deploying, verify:
- ✅ Home page loads correctly
- ✅ All card pages are accessible
- ✅ Images display properly
- ✅ Redirects work (test an old URL like `/no-blame-no-mercy`)
- ✅ Categories pages work
- ✅ Internal links between cards work
- ✅ Custom domain resolves correctly

## Environment Variables

No environment variables are required for this static site. All configuration is in the code.

## Troubleshooting

### Build fails
- Check Node version matches `.nvmrc`
- Clear Netlify cache: "Deploys" → "Trigger deploy" → "Clear cache and deploy site"

### Redirects not working
- Verify `_redirects` file exists in `public/` directory
- Check it appears in `dist/_redirects` after build
- Netlify processes `_redirects` automatically from the publish directory

### Images not loading
- Ensure `public/images/` contains all image files
- Check browser console for 404 errors
- Verify paths start with `/images/` (not relative paths)
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

# Xebia Essentials static site generator

This repository contains the static site generator for the [Xebia Essentials](https://essentials.xebia.com), along with all the cards' content and metadata. Each page contains [YAML](https://www.yaml.org/) [front matter](https://gridsome.org/docs/data-store-api/#preprocessing-markdown-frontmatter), two optional [markdown](https://daringfireball.net/projects/markdown) sections for the front and backsites of the [printed cards](https://xebia.com/books/xebia-essentials?utm_source=readme&utm_medium=web&utm_campaign=essentials) followed by a final markdown section that will be converted into the corresponding web page.
This repository contains the static site generator for the [Xebia Essentials](https://essentials.xebia.com), along with all the cards' content and metadata. Built with [Astro](https://astro.build) for maximum performance - 100% static with zero client-side JavaScript.

Each page contains [YAML](https://www.yaml.org/) front matter, two optional [markdown](https://daringfireball.net/projects/markdown) sections for the front and backsites of the [printed cards](https://xebia.com/books/xebia-essentials?utm_source=readme&utm_medium=web&utm_campaign=essentials) followed by a final markdown section that will be converted into the corresponding web page.

This results in the following layout:

Expand All @@ -25,7 +27,15 @@ This results in the following layout:
---
webpage

Configuration is done through Gridsome's `gridsome.config.js` and `gridsome.server.js`.
## Commands

```bash
npm run dev # Start development server
npm run build # Build for production
npm run preview # Preview production build
```

Configuration is in `astro.config.mjs` and content collections are defined in `src/content/config.ts`.

**Pull requests are more than welcome, see the [open issues](https://github.com/xebia/essentials/issues?state=open)!**

Expand Down Expand Up @@ -65,6 +75,10 @@ Build the static site using
npm run build
```

### Updating the redirects

Run ` ./generate_redirects.sh ./cards` from the root of this project to update the `_redirects` fils. This will enable netlify to redirect the "naked" URLs to the new nested structure

## Deploying to essentials.xebia.com

TBD
79 changes: 79 additions & 0 deletions _redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Auto-generated by generate_redirects.sh on 2025-08-18T12:21:10Z
# Root: ./cards
# Status: 301
# Slugify FROM: 0
/index / 301
/no-blame-no-mercy /base/collaboration/no-blame-no-mercy 301
/no-broken-windows /base/collaboration/no-broken-windows 301
/pair-programming /base/collaboration/pair-programming 301
/have-fun /base/collaboration/have-fun 301
/team-rhythm /base/collaboration/team-rhythm 301
/build-it-run-it /base/collaboration/build-it-run-it 301
/eliminate-waste /base/collaboration/eliminate-waste 301
/hurt-often /base/collaboration/hurt-often 301
/brutal-transparency /base/collaboration/brutal-transparency 301
/no-broken-builds /base/collaboration/no-broken-builds 301
/one-feature-at-a-time /base/collaboration/one-feature-at-a-time 301
/alone-time /base/collaboration/alone-time 301
/done /base/collaboration/done 301
/the-zone /base/collaboration/the-zone 301
/definition-of-ready /base/collaboration/definition-of-ready 301
/focus-on-flow /base/collaboration/focus-on-flow 301
/make-it-visible /base/collaboration/make-it-visible 301
/timebox /base/collaboration/timebox 301
/code-review /base/collaboration/code-review 301
/what-you-measure /base/collaboration/what-you-measure 301
/team-member-equality /base/collaboration/team-member-equality 301
/done-is-live /base/collaboration/done-is-live 301
/small-increments /base/craftsmanship/small-increments 301
/dry-principle /base/craftsmanship/dry-principle 301
/no-multitasking /base/craftsmanship/no-multitasking 301
/curiosity /base/craftsmanship/curiosity 301
/comment-with-care /base/craftsmanship/comment-with-care 301
/time-for-tech-debt /base/craftsmanship/time-for-tech-debt 301
/dare-to-say-no /base/craftsmanship/dare-to-say-no 301
/no-test-no-bugfix /base/craftsmanship/no-test-no-bugfix 301
/two-minute-rule /base/craftsmanship/two-minute-rule 301
/improve-continuously /base/craftsmanship/improve-continuously 301
/learn-a-new-language /base/craftsmanship/learn-a-new-language 301
/readable-code /base/craftsmanship/readable-code 301
/one-change-at-a-time /base/craftsmanship/one-change-at-a-time 301
/no-museum /base/craftsmanship/no-museum 301
/genchi-genbutsu /base/craftsmanship/genchi-genbutsu 301
/make-it-work-right-fast /base/craftsmanship/make-it-work-right-fast 301
/rich-communication /base/craftsmanship/rich-communication 301
/context-over-habit /base/craftsmanship/context-over-habit 301
/diagnose-before-cure /base/craftsmanship/diagnose-before-cure 301
/clean-build /base/craftsmanship/clean-build 301
/three-strikes /base/craftsmanship/three-strikes 301
/master-your-tools /base/craftsmanship/master-your-tools 301
/tests-are-specs /base/testing/tests-are-specs 301
/test-everything /base/testing/test-everything 301
/independent-tests /base/testing/independent-tests 301
/run-tests-automatically /base/testing/run-tests-automatically 301
/acceptance-criteria /base/testing/acceptance-criteria 301
/testing-is-shared-responsibility /base/testing/testing-is-shared-responsibility 301
/exploratory-testing /base/testing/exploratory-testing 301
/tdd-shapes-design /base/testing/tdd-shapes-design 301
/fail-fast /base/testing/fail-fast 301
/clean-logs /base/testing/clean-logs 301
/test-code-one /base/testing/test-code-one 301
/kiss /base/realisation/kiss 301
/integrate-early /base/realisation/integrate-early 301
/automate-everything /base/realisation/automate-everything 301
/secure-development /base/realisation/secure-development 301
/poutsma-principle /base/realisation/poutsma-principle 301
/shared-design-understanding /base/realisation/shared-design-understanding 301
/thirty-minute-methods /base/realisation/thirty-minute-methods 301
/maximize-cohesion-minimize-coupling /base/realisation/maximize-cohesion-minimize-coupling 301
/apis-are-forever /base/realisation/apis-are-forever 301
/separation-of-concerns /base/realisation/separation-of-concerns 301
/thread-safe /base/realisation/thread-safe 301
/non-functionals /base/realisation/non-functionals 301
/boy-scout-rule /base/realisation/boy-scout-rule 301
/fallacies-distributed-computing /base/realisation/fallacies-distributed-computing 301
/composition-over-inheritance /base/realisation/composition-over-inheritance 301
/hands-off-machine /base/realisation/hands-off-machine 301
/assertions /base/realisation/assertions 301
/focused-interfaces /base/realisation/focused-interfaces 301
/no-anemic-domain-model /base/realisation/no-anemic-domain-model 301
21 changes: 21 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';

// https://astro.build/config
export default defineConfig({
site: 'https://essentials.xebia.com',
output: 'static',
build: {
inlineStylesheets: 'auto',
},
integrations: [
tailwind({
applyBaseStyles: false,
}),
],
vite: {
build: {
cssCodeSplit: false, // Single CSS file for better caching
},
},
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
127 changes: 127 additions & 0 deletions generate_redirects.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
set -euo pipefail

# Generate a Netlify _redirects file mapping each markdown filename (no extension)
# to its nested path (no extension).
#
# Usage:
# ./generate_redirects.sh [root_dir] [output_file]
#
# Env vars:
# STATUS_CODE HTTP status to use (default: 301)
# SLUGIFY If "1", slugify the FROM path (lowercase, non-alnum -> "-") (default: 0)
# WARN_DUPLICATES If "1", warn when multiple files share the same FROM (default: 1)
#
# Examples:
# ./generate_redirects.sh content ./_redirects
# SLUGIFY=1 STATUS_CODE=302 ./generate_redirects.sh

ROOT="${1:-.}"
OUT="${2:-_redirects}"
STATUS_CODE="${STATUS_CODE:-301}"
SLUGIFY="${SLUGIFY:-0}"
WARN_DUPLICATES="${WARN_DUPLICATES:-1}"

if [ ! -d "$ROOT" ]; then
echo "Error: root directory \"$ROOT\" not found." >&2
exit 1
fi

tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

slugify() {
# lowercase, replace non-alnum with '-', trim leading/trailing '-'
# Note: uses POSIX tools
printf '%s' "$1" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//'
}

normalize_path() {
# remove duplicate slashes, resolve "./" segments in a simple way
# keep leading slash
# input: path starting with "/"
printf '%s' "$1" \
| sed -E 's#//+#/#g; s#/\./#/#g; s#^/\.?/?#/#'
}

# Write header
{
echo "# Auto-generated by generate_redirects.sh on $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
echo "# Root: $ROOT"
echo "# Status: $STATUS_CODE"
echo "# Slugify FROM: $SLUGIFY"
} > "$OUT"

# Collect raw rules into tmp as tab-separated: FROM <tab> TO <tab> STATUS
# - FROM: "/<filename-no-ext>" (possibly slugified)
# - TO: "/<relative/dir>/<filename-no-ext>" (index.md -> "/<relative/dir>" ; root index.md -> "/")
# Walk the tree safely with NUL delimiters
find "$ROOT" -type f \( -iname '*.md' -o -iname '*.mdx' \) -print0 \
| while IFS= read -r -d '' f; do
rel="${f#"$ROOT"/}"
base="$(basename "$f")"
name="${base%.*}" # filename without extension
dir="$(dirname "$rel")"

from_name="$name"
if [ "$SLUGIFY" = "1" ]; then
from_name="$(slugify "$from_name")"
fi

from="/$from_name"

# Build TO path
if [ "$name" = "index" ]; then
if [ "$dir" = "." ]; then
to="/"
else
to="/$dir"
fi
else
if [ "$dir" = "." ]; then
to="/$name"
else
to="/$dir/$name"
fi
fi

from="$(normalize_path "$from")"
to="$(normalize_path "$to")"

# Skip empty FROM (possible if slugify produced empty)
if [ -z "$from" ] || [ "$from" = "/" ]; then
# Still allow "/index" if name was index and SLUGIFY removed it? We skip to be safe.
continue
fi

# Write tab-separated for robust processing
printf "%s\t%s\t%s\n" "$from" "$to" "$STATUS_CODE" >> "$tmp"
done

# Deduplicate by FROM (keep first); optionally warn about duplicates
awk -F '\t' -v OFS='\t' -v warn="$WARN_DUPLICATES" '
{
from=$1
if (!(from in seen)) {
seen[from]=NR
lines[NR]=$0
order[NR]=from
} else if (warn) {
dups[from]++
}
}
END {
for (i=1; i<=NR; i++) {
if (i in lines) print lines[i]
}
if (warn) {
for (k in dups) {
printf "WARNING: duplicate source \"%s\". Only the first occurrence is kept.\n", k > "/dev/stderr"
}
}
}
' "$tmp" >> "$OUT"

echo "Wrote $(wc -l < "$OUT") lines to $OUT"
Loading