Skip to content

Commit 8d52891

Browse files
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
1 parent 0d72563 commit 8d52891

8 files changed

Lines changed: 80 additions & 26 deletions

File tree

.claude/skills/wp-audit.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ curl -sL "$SITE_URL" | grep -oP '<li[^>]*menu-item[^>]*>.*?</a>' | head -30
4545

4646
**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.
4747

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+
4850
### Step 3: Inventory all blog posts
4951

5052
```bash
@@ -197,7 +199,7 @@ done
197199

198200
Compile all findings into a structured summary:
199201
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)
201203
3. **Blog posts** — date, author, slug, featured image, comment count
202204
4. **Comments** — total count, distribution by post
203205
5. **Design tokens** — fonts, colors, gradients, spacing

.claude/skills/wp-to-ghpages.md

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
Port a WordPress site to Jekyll on GitHub Pages, preserving all content, comments, images, and visual design.
44

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+
522
## Prerequisites
623

724
- A completed WordPress audit (use the `wp-audit` skill first)
@@ -42,6 +59,8 @@ EOF
4259

4360
### Step 2: Create `_config.yml`
4461

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+
4564
```yaml
4665
title: "Site Title"
4766
description: "Site description"
@@ -139,7 +158,7 @@ Four layouts cover most WordPress sites:
139158

140159
**`_includes/footer.html`** — footer links, credit line.
141160

142-
**`_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.
143162

144163
**`_includes/comments.html`** — renders comments from `site.data.comments[page.slug]`. Handles nested replies.
145164

@@ -271,12 +290,14 @@ Create `_data/comments/<post-slug>.yml` for each post that has comments:
271290

272291
### Step 12: Create the blog listing page
273292

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:
275296
```html
276297
---
277298
layout: default
278299
title: Blog
279-
permalink: /blog/
300+
permalink: /posts/ # <-- MUST match the original WP blog listing URL
280301
header_image: /assets/images/header-blog.jpg
281302
---
282303
{% include page-header.html %}
@@ -296,20 +317,48 @@ header_image: /assets/images/header-blog.jpg
296317
</div>
297318
```
298319

299-
### Step 13: Test locally
320+
### 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.
300323

301324
```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
308-
# - Individual posts render correctly with code blocks, images, comments
309-
# - Mobile nav toggle works
310-
# - Page headers show correct background images with gradient overlay
325+
# Build and serve (use Docker if Ruby/Jekyll not installed locally)
326+
docker run --rm -d --network=host -v "$PWD:/srv/jekyll" \
327+
--name jekyll-serve jekyll/jekyll:4 \
328+
bash -c "bundle install --quiet && bundle exec jekyll serve --host 0.0.0.0 --port 4000"
311329
```
312330

331+
**Verification loop for EACH page:**
332+
333+
1. Screenshot the original WordPress page:
334+
```bash
335+
google-chrome --headless --disable-gpu --no-sandbox \
336+
--screenshot="reference-screenshots/original-<page>.png" \
337+
--window-size=1400,3000 "https://original-site.com/<page>/"
338+
```
339+
340+
2. Screenshot the Jekyll version:
341+
```bash
342+
google-chrome --headless --disable-gpu --no-sandbox \
343+
--screenshot="reference-screenshots/current-<page>.png" \
344+
--window-size=1400,3000 "http://localhost:4000/<page>/"
345+
```
346+
347+
3. View both screenshots and compare. Check for:
348+
- **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+
313362
### Step 14: Deploy
314363

315364
```bash
@@ -323,9 +372,11 @@ No GitHub Actions configuration needed. GitHub Pages detects the Jekyll project
323372

324373
## Common issues
325374

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.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ _site/
44
.jekyll-metadata
55
Gemfile.lock
66
vendor/
7+
reference-screenshots/
78
.bundle/
89
audit-screenshots/

_config.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ baseurl: ""
55

66
permalink: /:categories/:title/
77

8-
collections_dir: .
98
markdown: kramdown
109
highlighter: rouge
1110

_data/nav.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@
4040
url: /ordering/
4141

4242
- title: Blog
43-
url: /blog/
43+
url: /posts/

_includes/page-header.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ <h1>{{ page.header_title | default: page.title }}</h1>
55
{% if page.subtitle %}
66
<p class="page-subtitle">{{ page.subtitle }}</p>
77
{% endif %}
8-
{{ content }}
98
</div>
109
</div>

blog.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
title: Blog
44
header_title: Blog
55
header_image: /assets/images/header-blog.jpg
6-
permalink: /blog/
6+
permalink: /posts/
77
---
88

99
{% include page-header.html %}

index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ <h4><a href="{{ post.url | relative_url }}">{{ post.title }}</a></h4>
4949
</div>
5050
{% endfor %}
5151
</div>
52-
<a href="{{ '/blog/' | relative_url }}" class="btn">Read More</a>
52+
<a href="{{ '/posts/' | relative_url }}" class="btn">Read More</a>
5353
</div>
5454

5555
<!-- Location Section -->
@@ -75,8 +75,10 @@ <h3>Abu Dhabi</h3>
7575
<!-- Partner Logos -->
7676
<div class="container text-center">
7777
<div class="partner-logos">
78-
<a href="https://cgsb.as.nyu.edu"><img src="{{ '/assets/images/logos/cgsb_logo-1.png' | relative_url }}" alt="CGSB"></a>
78+
<a href="/"><img src="{{ '/assets/images/logos/gencore-logo-new.png' | relative_url }}" alt="Genomics Core"></a>
79+
<a href="http://as.nyu.edu/content/nyu-as/as/departments/biology/research/gsb.html"><img src="{{ '/assets/images/logos/cgsb_logo-1.png' | relative_url }}" alt="CGSB"></a>
7980
<a href="https://www.nyu.edu"><img src="{{ '/assets/images/logos/nyu.png' | relative_url }}" alt="NYU"></a>
81+
<a href="https://cgsb.abudhabi.nyu.edu/"><img src="{{ '/assets/images/logos/ad_cgsb_logo.jpg' | relative_url }}" alt="NYU Abu Dhabi CGSB"></a>
8082
</div>
8183
</div>
8284

0 commit comments

Comments
 (0)