Fix 404 errors on category pages via explicit permalink configuration#9
Fix 404 errors on category pages via explicit permalink configuration#9
Conversation
Co-authored-by: mk-ws-git <235080064+mk-ws-git@users.noreply.github.com>
Co-authored-by: mk-ws-git <235080064+mk-ws-git@users.noreply.github.com>
Co-authored-by: mk-ws-git <235080064+mk-ws-git@users.noreply.github.com>
Co-authored-by: mk-ws-git <235080064+mk-ws-git@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request fixes 404 errors on category pages by adding explicit permalink configuration and updating the category layout to use the correct data source. The changes address navigation issues where links with trailing slashes were generating 404s because Jekyll was creating URLs without them.
Changes:
- Added explicit
permalink: /categories/{category}/to all 8 category markdown files - Updated category layout to use
site.data.toolsinstead of the sparsesite.toolscollection - Commented out Jekyll theme configuration to use custom layouts
- Added missing Jekyll plugins to Gemfile (jekyll-feed, jekyll-seo-tag, jekyll-sitemap, kramdown-parser-gfm)
- Created .gitignore for build artifacts and vendor directory
- Added comprehensive documentation (TESTING_SUMMARY, DEPLOYMENT_GUIDE, IMPLEMENTATION_COMPLETE)
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| categories/planning.md | Added permalink configuration for consistent URL structure |
| categories/other.md | Added permalink configuration for consistent URL structure |
| categories/heat.md | Added permalink configuration for consistent URL structure |
| categories/green.md | Added permalink configuration for consistent URL structure |
| categories/flood.md | Added permalink configuration for consistent URL structure |
| categories/energy.md | Added permalink configuration for consistent URL structure |
| categories/data.md | Added permalink configuration for consistent URL structure |
| categories/coastal.md | Added permalink configuration for consistent URL structure |
| _layouts/category.html | Changed data source to site.data.tools and added inline tool card rendering with embedded styles |
| _config.yml | Commented out theme configuration to avoid conflicts with custom layouts |
| Gemfile | Added missing Jekyll plugin dependencies with version constraints |
| .gitignore | Added standard Jekyll build artifacts and vendor directories |
| TESTING_SUMMARY.md | Comprehensive testing documentation showing all 8 category pages working |
| DEPLOYMENT_GUIDE.md | Step-by-step deployment instructions for GitHub Pages |
| README.md | Updated with recent changes section and corrected local server instructions |
| IMPLEMENTATION_COMPLETE.md | Complete implementation summary with deployment checklist |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| </div> | ||
|
|
||
| <div class="card-footer"> | ||
| <a href="{{ tool.url }}" target="_blank" class="btn btn-sm btn-primary">View Tool →</a> |
There was a problem hiding this comment.
The external tool links open in a new tab with target="_blank" but are missing the rel="noopener noreferrer" attribute. This is a security best practice to prevent the opened page from accessing the opener window via window.opener, which could be exploited for phishing attacks. Add rel="noopener noreferrer" to the anchor tag.
| <a href="{{ tool.url }}" target="_blank" class="btn btn-sm btn-primary">View Tool →</a> | |
| <a href="{{ tool.url }}" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-primary">View Tool →</a> |
|
|
||
| <style> | ||
| .category-page { | ||
| padding: var(--spacing-3xl) 0; |
There was a problem hiding this comment.
The CSS variable --spacing-3xl is used but is not defined in the main stylesheet (assets/css/style.css). The stylesheet only defines spacing variables up to --spacing-2xl. This will cause the padding to fall back to the initial value, which may not match the intended design. Consider either defining --spacing-3xl in the stylesheet or using an existing variable like --spacing-2xl.
| padding: var(--spacing-3xl) 0; | |
| padding: var(--spacing-2xl) 0; |
| .card-body h3 { | ||
| font-size: 20px; | ||
| margin-bottom: var(--spacing-sm); | ||
| color: var(--text-dark); |
There was a problem hiding this comment.
The CSS variable --text-dark is used but is not defined in any of the stylesheets. The main stylesheet defines --dark-gray instead. This will cause the color to fall back to the initial value (likely black). Replace --text-dark with --dark-gray to match the existing color system, or define --text-dark in the stylesheet.
| color: var(--text-dark); | |
| color: var(--dark-gray); |
| .meta-item .value { | ||
| color: var(--text-dark); |
There was a problem hiding this comment.
The CSS variable --text-dark is used but is not defined in any of the stylesheets. The main stylesheet defines --dark-gray instead. This will cause the color to fall back to the initial value. Replace with --dark-gray to match the existing color system.
| <div class="tool-card" data-cost="{{ tool.cost }}" data-skill="{{ tool.skillLevel }}" data-coverage="{{ tool.coverage | join: ',' }}"> | ||
| <div class="card-header"> | ||
| <div class="card-badges"> | ||
| {% for category in tool.categories %} | ||
| <span class="badge badge-{{ category | downcase }}">{{ category }}</span> | ||
| {% endfor %} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div class="card-body"> | ||
| <h3>{{ tool.name }}</h3> | ||
| <p class="developer">{{ tool.developer }}</p> | ||
| <p class="description">{{ tool.description }}</p> | ||
|
|
||
| <div class="card-meta"> | ||
| <span class="meta-item"> | ||
| <span class="label">Cost:</span> | ||
| <span class="value">{{ tool.cost }}</span> | ||
| </span> | ||
| <span class="meta-item"> | ||
| <span class="label">Skill Level:</span> | ||
| <span class="value">{{ tool.skillLevel }}</span> | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div class="card-footer"> | ||
| <a href="{{ tool.url }}" target="_blank" class="btn btn-sm btn-primary">View Tool →</a> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
This layout duplicates the tool card HTML that already exists in _includes/tool-card.html. However, there's a data structure mismatch: the include expects fields like tool.access_model, tool.skill_level, tool.geographic_coverage, while this implementation uses tool.cost, tool.skillLevel, tool.coverage. The data in _data/tools.yml uses the latter naming convention. To maintain consistency and avoid duplication, consider updating the include to match the data structure, then using it here with {% include tool-card.html tool=tool %}.
| <div class="tool-card" data-cost="{{ tool.cost }}" data-skill="{{ tool.skillLevel }}" data-coverage="{{ tool.coverage | join: ',' }}"> | |
| <div class="card-header"> | |
| <div class="card-badges"> | |
| {% for category in tool.categories %} | |
| <span class="badge badge-{{ category | downcase }}">{{ category }}</span> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| <div class="card-body"> | |
| <h3>{{ tool.name }}</h3> | |
| <p class="developer">{{ tool.developer }}</p> | |
| <p class="description">{{ tool.description }}</p> | |
| <div class="card-meta"> | |
| <span class="meta-item"> | |
| <span class="label">Cost:</span> | |
| <span class="value">{{ tool.cost }}</span> | |
| </span> | |
| <span class="meta-item"> | |
| <span class="label">Skill Level:</span> | |
| <span class="value">{{ tool.skillLevel }}</span> | |
| </span> | |
| </div> | |
| </div> | |
| <div class="card-footer"> | |
| <a href="{{ tool.url }}" target="_blank" class="btn btn-sm btn-primary">View Tool →</a> | |
| </div> | |
| </div> | |
| {% include tool-card.html tool=tool %} |
| <style> | ||
| .category-page { | ||
| padding: var(--spacing-3xl) 0; | ||
| max-width: 1200px; | ||
| margin: 0 auto; | ||
| } | ||
|
|
||
| .category-page h1 { | ||
| font-size: 32px; | ||
| margin-bottom: var(--spacing-lg); | ||
| color: var(--primary-blue); | ||
| } | ||
|
|
||
| .category-intro { | ||
| margin-bottom: var(--spacing-2xl); | ||
| font-size: 16px; | ||
| line-height: 1.6; | ||
| color: #666; | ||
| } | ||
|
|
||
| .tool-grid { | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | ||
| gap: var(--spacing-lg); | ||
| } | ||
|
|
||
| .tool-card { | ||
| background: white; | ||
| border: 1px solid var(--border-gray); | ||
| border-radius: var(--radius-md); | ||
| padding: var(--spacing-lg); | ||
| transition: all 0.3s ease; | ||
| } | ||
|
|
||
| .tool-card:hover { | ||
| box-shadow: 0 4px 12px rgba(0, 81, 186, 0.1); | ||
| transform: translateY(-2px); | ||
| } | ||
|
|
||
| .card-header { | ||
| margin-bottom: var(--spacing-md); | ||
| } | ||
|
|
||
| .card-badges { | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: var(--spacing-sm); | ||
| } | ||
|
|
||
| .badge { | ||
| display: inline-block; | ||
| padding: 4px 12px; | ||
| font-size: 11px; | ||
| font-weight: 600; | ||
| text-transform: uppercase; | ||
| border-radius: 12px; | ||
| background: var(--secondary-blue); | ||
| color: var(--primary-blue); | ||
| } | ||
|
|
||
| .card-body h3 { | ||
| font-size: 20px; | ||
| margin-bottom: var(--spacing-sm); | ||
| color: var(--text-dark); | ||
| } | ||
|
|
||
| .developer { | ||
| font-size: 13px; | ||
| color: #999; | ||
| margin-bottom: var(--spacing-md); | ||
| } | ||
|
|
||
| .description { | ||
| font-size: 14px; | ||
| line-height: 1.6; | ||
| margin-bottom: var(--spacing-md); | ||
| color: #666; | ||
| } | ||
|
|
||
| .card-meta { | ||
| display: flex; | ||
| gap: var(--spacing-md); | ||
| font-size: 12px; | ||
| margin-bottom: var(--spacing-md); | ||
| } | ||
|
|
||
| .meta-item .label { | ||
| color: #999; | ||
| } | ||
|
|
||
| .meta-item .value { | ||
| color: var(--text-dark); | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| .card-footer { | ||
| padding-top: var(--spacing-md); | ||
| border-top: 1px solid var(--border-gray); | ||
| } | ||
|
|
||
| .btn-primary { | ||
| display: inline-block; | ||
| padding: 8px 16px; | ||
| background: var(--primary-blue); | ||
| color: white; | ||
| text-decoration: none; | ||
| border-radius: var(--radius-sm); | ||
| font-size: 13px; | ||
| font-weight: 600; | ||
| transition: background 0.3s ease; | ||
| } | ||
|
|
||
| .btn-primary:hover { | ||
| background: #003d8f; | ||
| } | ||
|
|
||
| @media (max-width: 768px) { | ||
| .tool-grid { | ||
| grid-template-columns: 1fr; | ||
| } | ||
| } | ||
| </style> |
There was a problem hiding this comment.
Embedding CSS styles within layout files reduces maintainability and violates the separation of concerns principle. The site already has a dedicated stylesheet structure (assets/css/cards.css, style.css). These styles should be moved to an appropriate CSS file, likely assets/css/cards.css or a new assets/css/category.css file. This would also resolve the issue with undefined CSS variables and make the styles reusable across different layouts.
| <div class="card-header"> | ||
| <div class="card-badges"> | ||
| {% for category in tool.categories %} | ||
| <span class="badge badge-{{ category | downcase }}">{{ category }}</span> |
There was a problem hiding this comment.
The badge class names generated by badge-{{ category | downcase }} won't match the CSS class names defined in the stylesheet for some categories. For example, the 'green' category will generate .badge-green, but the stylesheet defines .badge-greeninfra. Similarly, 'data' generates .badge-data but the stylesheet has .badge-dataplat. This will cause these badges to lose their styling and appear unstyled. Either update the stylesheet to add .badge-green and .badge-data classes, or change the category values in the data file to match the existing CSS classes.
| <span class="badge badge-{{ category | downcase }}">{{ category }}</span> | |
| {% assign category_class = category | downcase %} | |
| {% if category_class == 'green' %} | |
| {% assign category_class = 'greeninfra' %} | |
| {% elsif category_class == 'data' %} | |
| {% assign category_class = 'dataplat' %} | |
| {% endif %} | |
| <span class="badge badge-{{ category_class }}">{{ category }}</span> |
| <div class="card-header"> | ||
| <div class="card-badges"> | ||
| {% for category in tool.categories %} | ||
| <span class="badge badge-{{ category | downcase }}">{{ category }}</span> |
There was a problem hiding this comment.
The 'other' category used in the data file doesn't have a corresponding .badge-other style defined in the stylesheet. This will cause badges for the 'other' category to appear unstyled. Add a .badge-other style definition to assets/css/style.css to ensure consistent badge styling across all categories.
| <span class="badge badge-{{ category | downcase }}">{{ category }}</span> | |
| {% assign category_class = category | downcase %} | |
| {% if category_class == "other" %} | |
| {% assign category_class = "default" %} | |
| {% endif %} | |
| <span class="badge badge-{{ category_class }}">{{ category }}</span> |
Category pages were generating URLs without trailing slashes (
/categories/green) while navigation links expected them with trailing slashes (/categories/green/), causing 404s.Changes
Category permalinks (8 files)
permalink: /categories/{category}/to all category markdown frontmatter/categories/{category}/index.htmlstructureCategory layout (
_layouts/category.html)site.toolscollection tosite.data.toolsYAMLpage.categoryand renders cards inlineBuild configuration
jekyll-theme-minimalin_config.yml(using custom layouts)Gemfile:jekyll-feed,jekyll-seo-tag,jekyll-sitemap,kramdown-parser-gfm.gitignoreforvendor/,_site/,.bundle/Example
Before:
After:
This matches the expected URL pattern in
categories/index.mdwhich links to/categories/green/.Original prompt
This pull request was created from Copilot chat.
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.