Skip to content
Merged
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: 11 additions & 3 deletions .github/actions/setup-hugo/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ description: 'Install Hugo + Bun, cache dependencies and build artifacts'
inputs:
hugo-version:
description: 'Hugo version'
default: '0.149.1'
default: '0.160.1'
environment:
description: 'Hugo build environment'
default: 'development'
default: 'production'
output-dir:
description: 'Hugo build output directory'
default: '_dest/public-test'
base-url:
description: 'Hugo base URL (default: /)'
default: '/'
runs:
using: 'composite'
steps:
- uses: peaceiris/actions-hugo@v3
- uses: jetthoughts/actions-hugo@feat/node24
with:
hugo-version: ${{ inputs.hugo-version }}
extended: true
Expand Down Expand Up @@ -48,3 +54,5 @@ runs:
env:
HUGO_CACHEDIR: /tmp/hugo_cache
ENVIRONMENT: ${{ inputs.environment }}
OUTPUT_DIR: ${{ inputs.output-dir }}
BASE_URL: ${{ inputs.base-url }}
18 changes: 12 additions & 6 deletions .github/workflows/_hugo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
uses: jetthoughts/actions-hugo@feat/node24
with:
hugo-version: 0.149.1
hugo-version: 0.160.1
extended: true

- name: Checkout
Expand All @@ -28,7 +28,7 @@ jobs:

- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
uses: actions/configure-pages@v6

# Tier 1: Dependencies cache (changes infrequently)
- uses: actions/cache@v5
Expand Down Expand Up @@ -78,8 +78,14 @@ jobs:
--baseURL "https://jetthoughts.com/" \
--environment production

- name: Upload artifact
uses: actions/upload-pages-artifact@v4
- name: Remove blog media from artifact
run: |
find public/blog/ -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.gif' -o -iname '*.webp' \) -delete
echo "Cleaned blog media. Artifact size:"
du -sh public/

- name: Upload pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: ./public

Expand All @@ -94,4 +100,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
19 changes: 19 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,22 @@ defaults:
jobs:
build_and_deploy:
uses: ./.github/workflows/_hugo.yml

unit_tests:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6

- uses: ruby/setup-ruby@v1
with:
ruby-version: '4.0'
bundler-cache: true

- uses: ./.github/actions/setup-hugo

- run: bundle exec rake test:unit
env:
PRECOMPILED_ASSETS: '1'
HUGO_DEFAULT_PATH: _dest/public-test
28 changes: 7 additions & 21 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
---
name: Tests
name: Screenshot Tests

on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
screenshots:
description: 'Run screenshot tests (slow, requires Hugo build)'
type: boolean
default: false
default: true
update-baselines:
description: 'Re-record screenshot baselines and commit'
type: boolean
Expand All @@ -20,26 +18,10 @@ permissions:
pull-requests: write

concurrency:
group: tests-${{ github.event.pull_request.number || github.ref }}
group: screenshots-${{ github.ref }}
cancel-in-progress: true

jobs:
unit:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6

- uses: ruby/setup-ruby@v1
with:
ruby-version: '4.0'
bundler-cache: true

- uses: ./.github/actions/setup-hugo

- run: bundle exec rake test:unit

screenshots:
name: Screenshot Tests
if: ${{ inputs.screenshots || inputs.update-baselines }}
Expand All @@ -63,12 +45,16 @@ jobs:
run: bundle exec rake test
env:
SCREENSHOT_DRIVER: vips
PRECOMPILED_ASSETS: '1'
HUGO_DEFAULT_PATH: _dest/public-test

- name: Record baselines
if: ${{ inputs.update-baselines }}
run: FORCE_SCREENSHOT_UPDATE=true bundle exec rake test
env:
SCREENSHOT_DRIVER: vips
PRECOMPILED_ASSETS: '1'
HUGO_DEFAULT_PATH: _dest/public-test

- name: Commit updated baselines
if: ${{ inputs.update-baselines }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,4 @@ docs/projects/2509-css-migration/50-59-execution/
*.local.*
.cache
_workspace
.claude/scheduled_tasks.lock
13 changes: 11 additions & 2 deletions bin/hugo-build
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@
set -euo pipefail

# Default settings
OUTPUT_DIR="_dest/public-dev"
OUTPUT_DIR=${OUTPUT_DIR:-_dest/public-dev}
ENVIRONMENT=${ENVIRONMENT:-development}

BASE_URL=${BASE_URL:-}

echo "Building Hugo site..."
echo "Environment: $ENVIRONMENT"
echo "Output: $OUTPUT_DIR"
hugo build --noBuildLock --environment "$ENVIRONMENT" --destination "$OUTPUT_DIR"

BUILD_DRAFTS=${BUILD_DRAFTS:-}

HUGO_ARGS=(hugo build --noBuildLock --environment "$ENVIRONMENT" --destination "$OUTPUT_DIR")
[ -n "$BASE_URL" ] && HUGO_ARGS+=(--baseURL "$BASE_URL")
[ -n "$BUILD_DRAFTS" ] && HUGO_ARGS+=(--buildDrafts)

"${HUGO_ARGS[@]}"

echo "✓ Build complete"
4 changes: 4 additions & 0 deletions config/_default/hugo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ disableKinds = []
changeFreq = 'weekly'
priority = 0.5

[params.cdn]
enabled = false # enabled per-environment in config/production/hugo.toml
rawBase = "raw.githubusercontent.com/jetthoughts/jetthoughts.github.io/master/content/"

[params]
email="info@jetthoughts.com"
phone="+1 754 216 9568"
Expand Down
3 changes: 3 additions & 0 deletions config/production/hugo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ disableKinds = []
# Production environment
environment = "production"

[params.cdn]
enabled = true

[outputs]
home = ["HTML"] # Only build HTML, skip JSON/RSS in tests
section = ["HTML"]
Expand Down
62 changes: 62 additions & 0 deletions docs/workflows/cdn-image-proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# CDN Image Proxy for Production

Solves the GitHub Pages 1 GB artifact limit by serving blog images from a CDN proxy in production, while keeping local Hugo image processing for development.

## How It Works

1. **Development** (`hugo server`): Images processed locally by Hugo — no change.
2. **Production** (`--environment production`): Templates emit CDN URLs instead of local paths via `partials/cdn/url.html`.
3. **GitHub Actions**: After Hugo build, blog media files are deleted from `public/blog/` before uploading the artifact.

## Configuration

**Default** (`config/_default/hugo.toml`) — CDN disabled:
```toml
[params.cdn]
enabled = false
rawBase = "raw.githubusercontent.com/jetthoughts/jetthoughts.github.io/master/content/"
```

**Production** (`config/production/hugo.toml`) — CDN enabled:
```toml
[params.cdn]
enabled = true
```

Gate in templates: `{{ if site.Params.cdn.enabled }}`

## Shared Partial

`themes/beaver/layouts/partials/cdn/url.html` — single source for CDN URL construction:

```text
{{ partial "cdn/url" (dict "page" $page "resource" $resource "params" "w=400&output=webp&q=80") }}
→ "https://wsrv.nl/?url=raw.githubusercontent.com/.../image.jpg&w=400&output=webp&q=80"
```

## Templates Modified

| Template | CDN behavior |
|----------|-------------|
| `_markup/render-image.html` | `<picture>` srcset via wsrv.nl (webp+jpeg, 400/800/1200/1600w) |
| `partials/blog/img-cropped.html` | Retina thumbnail via wsrv.nl |
| `partials/seo/enhanced-meta-tags.html` | OG image (1200×630) via wsrv.nl |
| `_shortcodes/img.html` | Direct image via wsrv.nl |

**Note:** Root `layouts/` overrides `themes/beaver/layouts/`. Both `enhanced-meta-tags.html` copies must stay in sync.

## wsrv.nl Params

- `&w=N` — width resize
- `&h=N` — height resize
- `&output=webp|jpeg` — format conversion
- `&q=N` — quality (80 content, 85 OG, 90 thumbnails)
- `&fit=cover` — crop to exact dimensions (OG images)

## Not Yet Covered

Non-blog image templates (assets from `assets/`/`static/`, unaffected by blog media cleanup):

- `partials/img/generic.html` (hero, homepage, testimonials)
- `partials/img/resize.html`
- `partials/page/cover_image.html`
68 changes: 21 additions & 47 deletions layouts/partials/seo/enhanced-meta-tags.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,57 +97,32 @@
{{- end -}}
{{- end -}}

{{/* Image Handling - metatags.image is primary, then cover_image/cover/featured_image */}}
{{/* Image Handling — try frontmatter fields in priority order */}}
{{- $image := "" -}}
{{- $imageWidth := "1200" -}}
{{- $imageHeight := "630" -}}

{{/* Try metatags.image first (primary convention) */}}
{{- with .Params.metatags.image -}}
{{- $resource := $page.Resources.Get . -}}
{{- if $resource -}}
{{- $optimized := $resource.Resize "1200x630 jpg q90" -}}
{{- $image = $optimized.Permalink -}}
{{- $imageWidth = $optimized.Width -}}
{{- $imageHeight = $optimized.Height -}}
{{- end -}}
{{- end -}}

{{/* Try cover_image */}}
{{- if not $image -}}
{{- with .Params.cover_image -}}
{{- $resource := $page.Resources.Get . -}}
{{- if $resource -}}
{{- $optimized := $resource.Resize "1200x630 jpg q90" -}}
{{- $image = $optimized.Permalink -}}
{{- $imageWidth = $optimized.Width -}}
{{- $imageHeight = $optimized.Height -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/* Try cover (alias) */}}
{{- if not $image -}}
{{- with .Params.cover -}}
{{- $resource := $page.Resources.Get . -}}
{{- if $resource -}}
{{- $optimized := $resource.Resize "1200x630 jpg q90" -}}
{{- $image = $optimized.Permalink -}}
{{- $imageWidth = $optimized.Width -}}
{{- $imageHeight = $optimized.Height -}}
{{- $imageFields := slice "metatags.image" "cover_image" "cover" "featured_image" -}}
{{- range $field := $imageFields -}}
{{- if not $image -}}
{{- $value := "" -}}
{{- if eq $field "metatags.image" -}}
{{- $value = $page.Params.metatags.image -}}
{{- else -}}
{{- $value = index $page.Params $field -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/* Try featured_image (legacy) */}}
{{- if not $image -}}
{{- with .Params.featured_image -}}
{{- $resource := $page.Resources.Get . -}}
{{- if $resource -}}
{{- $optimized := $resource.Resize "1200x630 jpg q90" -}}
{{- $image = $optimized.Permalink -}}
{{- $imageWidth = $optimized.Width -}}
{{- $imageHeight = $optimized.Height -}}
{{- with $value -}}
{{- $resource := $page.Resources.Get . -}}
{{- if $resource -}}
{{- if site.Params.cdn.enabled -}}
{{- $image = partial "cdn/url" (dict "page" $page "resource" $resource "params" "w=1200&h=630&fit=cover&output=webp&q=85") -}}
{{- else -}}
{{- $optimized := $resource.Resize "1200x630 jpg q90" -}}
{{- $image = $optimized.Permalink -}}
{{- $imageWidth = $optimized.Width -}}
{{- $imageHeight = $optimized.Height -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
Expand All @@ -162,7 +137,6 @@
<meta property="og:image" content="{{ $image }}" />
<meta property="og:image:width" content="{{ $imageWidth }}" />
<meta property="og:image:height" content="{{ $imageHeight }}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="{{ $image }}" />
{{- end -}}

Expand Down
Loading