You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix content duplication, broken posts, and URL mismatches
- Remove {{ content }} from page-header.html include (caused every
page to render its body twice — once in the header overlay, once
in the main body)
- Remove collections_dir from _config.yml (silently broke post
discovery with github-pages gem — zero posts, no error)
- Change blog permalink from /blog/ to /posts/ to match original
WordPress URL, update nav.yml and homepage link
- Add all 4 partner logos to homepage (was showing only 2)
- Update wp-to-ghpages and wp-audit skills with lessons learned:
page-by-page workflow, screenshot comparison loop, URL preservation,
and documented the silent-failure traps
**Gotcha: The nav menu structure is in the page HTML, not the API.** The default WordPress REST API doesn't expose menus (requires a plugin). Extract the full nav including dropdowns from the HTML.
47
47
48
+
**CRITICAL: Extract exact URLs from navigation hrefs.** Use WebFetch or curl to read the homepage and extract every nav link's exact href. These URLs are what the Jekyll permalinks must match — never guess a URL like `/blog/` when the actual link is `/posts/`. Record the full URL map in the audit manifest so the port skill can set permalinks correctly.
49
+
48
50
### Step 3: Inventory all blog posts
49
51
50
52
```bash
@@ -197,7 +199,7 @@ done
197
199
198
200
Compile all findings into a structured summary:
199
201
1.**Sitemap** — all pages with hierarchy, URLs, and WP IDs
200
-
2.**Nav menu structure** — with dropdowns
202
+
2.**Nav menu structure** — with dropdowns and **exact href URLs** extracted from the HTML (these become the Jekyll permalinks)
201
203
3.**Blog posts** — date, author, slug, featured image, comment count
202
204
4.**Comments** — total count, distribution by post
Copy file name to clipboardExpand all lines: .claude/skills/wp-to-ghpages.md
+70-19Lines changed: 70 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,23 @@
2
2
3
3
Port a WordPress site to Jekyll on GitHub Pages, preserving all content, comments, images, and visual design.
4
4
5
+
## Critical: Work one page at a time
6
+
7
+
Do NOT attempt a bulk port of all pages at once. Port one page at a time using this loop:
8
+
9
+
1. Screenshot the original WordPress page (headless Chrome)
10
+
2. Build/fix the Jekyll version of that page
11
+
3. Rebuild the site and screenshot the new version
12
+
4. Compare the two screenshots side-by-side
13
+
5. Fix discrepancies and repeat steps 3-4 until the page matches
14
+
6. Move to the next page
15
+
16
+
Keep the original screenshot on hand throughout — always compare against the real site, not your memory of it.
17
+
18
+
## Critical: Preserve all original URLs
19
+
20
+
Every page permalink must match the original WordPress URL path exactly. Extract URLs from the site's navigation links (never guess URLs). If the WordPress blog listing is at `/posts/`, the Jekyll page must use `permalink: /posts/`. If ordering is at `/home-page/ordering/`, match that too (or decide explicitly to change it — don't accidentally drift).
21
+
5
22
## Prerequisites
6
23
7
24
- A completed WordPress audit (use the `wp-audit` skill first)
@@ -42,6 +59,8 @@ EOF
42
59
43
60
### Step 2: Create `_config.yml`
44
61
62
+
**IMPORTANT: Do NOT include `collections_dir` in the config.** Setting `collections_dir: .` (or any value) breaks post discovery with the github-pages gem — Jekyll will silently find zero posts and render empty blog sections with no error message. Just omit the key entirely; the default behavior is correct.
63
+
45
64
```yaml
46
65
title: "Site Title"
47
66
description: "Site description"
@@ -139,7 +158,7 @@ Four layouts cover most WordPress sites:
**`_includes/page-header.html`** — background image + gradient overlay, page title, optional subtitle. Reads `page.header_image` for the background.
161
+
**`_includes/page-header.html`** — background image + gradient overlay, page title, optional subtitle. Reads `page.header_image` for the background. **CRITICAL: Do NOT put `{{ content }}` in this include.** The include renders inside the layout, which already renders `{{ content }}`. If the include also renders `{{ content }}`, every page's body will appear twice — once inside the header overlay (broken) and once in the main body. The page-header include should ONLY render `page.title`, `page.subtitle`, and the background image.
143
162
144
163
**`_includes/comments.html`** — renders comments from `site.data.comments[page.slug]`. Handles nested replies.
145
164
@@ -271,12 +290,14 @@ Create `_data/comments/<post-slug>.yml` for each post that has comments:
271
290
272
291
### Step 12: Create the blog listing page
273
292
274
-
Create `blog.html` in the root:
293
+
**First, find the original blog listing URL.** Fetch the homepage HTML and extract the Blog nav link href — do NOT guess `/blog/` or `/posts/`. Use whatever URL the original site uses.
294
+
295
+
Create `blog.html` in the root with `permalink` matching the original URL:
275
296
```html
276
297
---
277
298
layout: default
278
299
title: Blog
279
-
permalink: /blog/
300
+
permalink: /posts/ # <-- MUST match the original WP blog listing URL
### Step 13: Test locally — page-by-page screenshot comparison
321
+
322
+
This is the most important step. Do not skip or rush it. Build the site, serve it, and compare every page against the original using headless Chrome screenshots.
300
323
301
324
```bash
302
-
bundle install
303
-
bundle exec jekyll serve
304
-
# Visit http://localhost:4000 and verify:
305
-
# - Homepage loads with hero, nav, blog previews, location maps
306
-
# - All nav links work (including dropdown sub-pages)
307
-
# - Blog listing shows all posts with featured images
- **Content duplication** — anything appearing twice means a layout/include bug
349
+
- **Missing content** — empty sections (especially blog post lists) mean posts aren't being found
350
+
- **Wrong content** — content from another page appearing means layout contamination
351
+
- **Missing images** — broken image paths
352
+
- **Layout differences** — wrong spacing, missing sections, wrong order
353
+
354
+
4. Fix issues, rebuild, re-screenshot, and compare again. Repeat until the page matches.
355
+
356
+
**Common traps that cause silent failures:**
357
+
- `collections_dir`in `_config.yml` → zero posts found, no error
358
+
- `{{ content }}`in `page-header.html` include → content renders twice on every page
359
+
- Blog listing permalink doesn't match nav link → 404 on blog page
360
+
- Missing `featured_image` in post front matter → empty blog cards on homepage
361
+
313
362
### Step 14: Deploy
314
363
315
364
```bash
@@ -323,9 +372,11 @@ No GitHub Actions configuration needed. GitHub Pages detects the Jekyll project
323
372
324
373
## Common issues
325
374
326
-
1. **GitHub Pages gem version conflicts** — `github-pages` pins specific gem versions. Don't add gems that conflict.
327
-
2. **Sass deprecation warnings** — GitHub Pages uses an older Sass version. Avoid newer Sass features (`@use`, `@forward`). Stick with `@import`.
328
-
3. **Large files** — GitHub has a 100MB file limit. Compress images before committing.
329
-
4. **Permalink mismatches** — WordPress URLs must match Jekyll permalinks exactly for SEO. Set `permalink:` in front matter to match the original URL path.
330
-
5. **Missing images** — After porting, grep for any remaining `wp-content/uploads` references and replace them.
331
-
6. **Comment slugs** — The comment YAML filename must match the post's `page.slug` (derived from the filename by default). If you set a custom `slug` in front matter, use `comment_slug` to override.
375
+
1. **`collections_dir` breaks post discovery** — Do NOT set `collections_dir: .` (or any value) in `_config.yml`. With the github-pages gem, this silently prevents Jekyll from finding `_posts/`. You'll get zero posts with no error message — blog sections render empty, homepage blog previews show nothing. Just omit the key entirely.
376
+
2. **`{{ content }}` in page-header include causes duplication** — If `_includes/page-header.html` contains `{{ content }}`, every page's body renders twice: once inside the header overlay (mangled, overlapping the hero image) and once in the normal position. The include should only render title and subtitle — never `{{ content }}`.
377
+
3. **Permalink mismatches break navigation** — Extract every URL from the original site's nav links. Set each Jekyll page's `permalink:` to match exactly. Don't guess — fetch the page and read the hrefs. If the WP blog is at `/posts/`, use `permalink: /posts/`, not `/blog/`.
378
+
4. **GitHub Pages gem version conflicts** — `github-pages` pins specific gem versions. Don't add gems that conflict.
379
+
5. **Sass deprecation warnings** — GitHub Pages uses an older Sass version. Avoid newer Sass features (`@use`, `@forward`). Stick with `@import`.
380
+
6. **Large files** — GitHub has a 100MB file limit. Compress images before committing.
381
+
7. **Missing images** — After porting, grep for any remaining `wp-content/uploads` references and replace them.
382
+
8. **Comment slugs** — The comment YAML filename must match the post's `page.slug` (derived from the filename by default). If you set a custom `slug` in front matter, use `comment_slug` to override.
0 commit comments