Add default error.hbs and error-404.hbs templates#145
Conversation
Source currently doesn't ship any error templates, so Ghost falls back
to an unbranded default. This adds two minimal, on-brand error pages
following the same convention as Casper:
- error.hbs (standalone, lightweight): handles all non-404 errors (500,
etc.). No partials or API calls so it stays renderable when something
on the server is degraded.
- error-404.hbs (extends default.hbs): keeps the visitor inside the
theme chrome and suggests 3 recent posts to help them re-engage.
Both templates use only existing translation keys ('Go to the front
page →', 'Recent posts', 'Theme errors'), so no locale changes are
required.
WalkthroughThis PR adds two Handlebars templates: error-404.hbs (renders a 404 page showing statusCode and a localized “Page not found”, links to the front page, and optionally lists up to 3 recent posts via the post-card partial) and error.hbs (generic error page that shows statusCode/message, optional errorDetails, integrates theme head/foot, and uses inline CSS from Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@error.hbs`:
- Around line 19-27: error.hbs sets --background-color
({{`@custom.site_background_color`}}) but never applies the contrast class used in
default.hbs, so add the same contrast-class logic: determine whether the
provided {{`@custom.site_background_color`}} is light or dark (reusing the
helper/logic from default.hbs or the theme's color-to-contrast utility) and add
either "has-dark-text" or "has-light-text" to the <body class="{{body_class}}">
element so foreground styles follow the correct contrast; locate the
contrast-detection used in default.hbs (or its helper) and call it here, or
insert a minimal inline script that computes luminance from
{{`@custom.site_background_color`}} and sets the body class accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f63d0826-8cb2-437f-ab7d-06ee47beaea1
📒 Files selected for processing (2)
error-404.hbserror.hbs
| <style> | ||
| :root { | ||
| --background-color: {{@custom.site_background_color}} | ||
| } | ||
| </style> | ||
| {{ghost_head}} | ||
| </head> | ||
| <body class="{{body_class}}"> | ||
|
|
There was a problem hiding this comment.
Missing contrast-class handling can make error text unreadable on custom backgrounds.
error.hbs sets --background-color but doesn’t apply the has-dark-text/has-light-text class behavior used by default.hbs, so foreground contrast can break on some custom colors.
Suggested fix
<body class="{{body_class}}">
+<script>
+ var accentColor = getComputedStyle(document.documentElement).getPropertyValue('--background-color').trim().slice(1);
+ if (accentColor.length === 3) {
+ accentColor = accentColor[0] + accentColor[0] + accentColor[1] + accentColor[1] + accentColor[2] + accentColor[2];
+ }
+ var r = parseInt(accentColor.substr(0, 2), 16);
+ var g = parseInt(accentColor.substr(2, 2), 16);
+ var b = parseInt(accentColor.substr(4, 2), 16);
+ var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
+ document.documentElement.className = `has-${yiq >= 128 ? 'dark' : 'light'}-text`;
+</script>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <style> | |
| :root { | |
| --background-color: {{@custom.site_background_color}} | |
| } | |
| </style> | |
| {{ghost_head}} | |
| </head> | |
| <body class="{{body_class}}"> | |
| <style> | |
| :root { | |
| --background-color: {{`@custom.site_background_color`}} | |
| } | |
| </style> | |
| {{ghost_head}} | |
| </head> | |
| <body class="{{body_class}}"> | |
| <script> | |
| var accentColor = getComputedStyle(document.documentElement).getPropertyValue('--background-color').trim().slice(1); | |
| if (accentColor.length === 3) { | |
| accentColor = accentColor[0] + accentColor[0] + accentColor[1] + accentColor[1] + accentColor[2] + accentColor[2]; | |
| } | |
| var r = parseInt(accentColor.substr(0, 2), 16); | |
| var g = parseInt(accentColor.substr(2, 2), 16); | |
| var b = parseInt(accentColor.substr(4, 2), 16); | |
| var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; | |
| document.documentElement.className = `has-${yiq >= 128 ? 'dark' : 'light'}-text`; | |
| </script> | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@error.hbs` around lines 19 - 27, error.hbs sets --background-color
({{`@custom.site_background_color`}}) but never applies the contrast class used in
default.hbs, so add the same contrast-class logic: determine whether the
provided {{`@custom.site_background_color`}} is light or dark (reusing the
helper/logic from default.hbs or the theme's color-to-contrast utility) and add
either "has-dark-text" or "has-light-text" to the <body class="{{body_class}}">
element so foreground styles follow the correct contrast; locate the
contrast-detection used in default.hbs (or its helper) and call it here, or
insert a minimal inline script that computes luminance from
{{`@custom.site_background_color`}} and sets the body class accordingly.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b9d85f1f39
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| <section class="gh-content is-body"> | ||
| <p> | ||
| <a href="{{@site.url}}" class="gh-button">{{t "Go to the front page →"}}</a> |
There was a problem hiding this comment.
Remove theme helpers from generic error template
In error.hbs, using {{t}} makes the generic error page depend on a theme helper even though Ghost’s error-template contract expects error.hbs to avoid theme helpers (except asset) to reduce cascading failures and misleading diagnostics during 5xx handling. If the original failure is related to helper/theme rendering, this can cause the error template itself to fail or mask the root cause. Replace translated helper output here with static text (or move helper-dependent UX to error-404.hbs, where helpers are allowed).
Useful? React with 👍 / 👎.
The {{message}} helper outputs Ghost's internal English error message,
which doesn't get translated. For the 404 case the message is always
roughly 'Page not found', so we replace it with a properly i18n-able
{{t "Page not found"}} string and register the key across all locale
files (with the French translation included).
The .gh-feed needs to be wrapped in <main class="gh-main"> so it inherits the proper grid-column placement (3 / span 12) inside the 16-column .gh-container-inner grid. This mirrors how the post-list partial structures the same section.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@error-404.hbs`:
- Around line 34-40: The template introduces a second main landmark by wrapping
the suggested posts in <main class="gh-main">; replace that inner <main> with a
non-landmark element (e.g., <div> or <section>) while preserving class="gh-main"
and the existing {{`#foreach` suggested}} ... {{> "post-card" lazyLoad=true}}
block so styling and behavior remain unchanged; update the element surrounding
the suggested feed in error-404.hbs where the inner main is declared.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6151d2f2-8b24-4d6f-bdfb-8e122439562a
📒 Files selected for processing (15)
error-404.hbslocales/context.jsonlocales/de-CH.jsonlocales/de.jsonlocales/en.jsonlocales/fr.jsonlocales/ga.jsonlocales/gd.jsonlocales/nl.jsonlocales/pt-BR.jsonlocales/sv.jsonlocales/tr.jsonlocales/uk.jsonlocales/zh-Hant.jsonlocales/zh.json
✅ Files skipped from review due to trivial changes (11)
- locales/ga.json
- locales/fr.json
- locales/sv.json
- locales/nl.json
- locales/en.json
- locales/zh.json
- locales/gd.json
- locales/context.json
- locales/zh-Hant.json
- locales/pt-BR.json
- locales/de-CH.json
| <main class="gh-main"> | ||
| <div class="gh-feed"> | ||
| {{#foreach suggested}} | ||
| {{> "post-card" lazyLoad=true}} | ||
| {{/foreach}} | ||
| </div> | ||
| </main> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify how many main landmarks are present in the template
FILE="$(fd '^error-404\.hbs$' | head -n1)"
rg -n '<main\b|</main>' "$FILE"Repository: TryGhost/Source
Length of output: 164
🏁 Script executed:
#!/bin/bash
FILE="$(fd '^error-404\.hbs$' | head -n1)"
echo "FILE=$FILE"
nl -ba "$FILE" | sed -n '1,80p'Repository: TryGhost/Source
Length of output: 137
🏁 Script executed:
#!/bin/bash
FILE="$(fd '^error-404\.hbs$' | head -n1)"
echo "FILE=$FILE"
echo "---- all <main> / </main> occurrences ----"
rg -n '<main\b|</main>' "$FILE"
echo "---- lines 1-80 with numbers (awk) ----"
awk '{printf "%d:%s\n", NR, $0}' "$FILE" | sed -n '1,80p'Repository: TryGhost/Source
Length of output: 1786
🏁 Script executed:
#!/bin/bash
# Verify how many main landmarks are present in the template
FILE="$(fd '^error-404\.hbs$' | head -n1)"
rg -n '<main\b|</main>' "$FILE"Repository: TryGhost/Source
Length of output: 164
Avoid a second <main> landmark in error-404.hbs
error-404.hbs already has a <main class="gh-main"> at the top, and lines 34-40 add another <main class="gh-main"> around the “Recent posts” feed, creating landmark ambiguity for screen readers. Switch the inner wrapper to a non-landmark element while keeping the styling class.
♻️ Proposed fix
- <main class="gh-main">
+ <div class="gh-main">
<div class="gh-feed">
{{`#foreach` suggested}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
</div>
- </main>
+ </div>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@error-404.hbs` around lines 34 - 40, The template introduces a second main
landmark by wrapping the suggested posts in <main class="gh-main">; replace that
inner <main> with a non-landmark element (e.g., <div> or <section>) while
preserving class="gh-main" and the existing {{`#foreach` suggested}} ... {{>
"post-card" lazyLoad=true}} block so styling and behavior remain unchanged;
update the element surrounding the suggested feed in error-404.hbs where the
inner main is declared.
Problem
Source currently doesn't ship any error templates. When a visitor hits a 404 or any other error, Ghost falls back to an unbranded default error page that breaks the visual continuity of the site.
Solution
Two minimal, on-brand error templates following the same convention as Casper.
error-404.hbsExtends
default.hbsso the visitor stays inside the theme's navigation and footer. Shows the status code, a friendly translatable "Page not found" message, a "Go to the front page" button, and a strip of 3 recent posts to help the visitor re-engage rather than leave.error.hbsStandalone (doesn't extend
default.hbs) and intentionally lightweight. Handles all non-404 errors (500, etc.). No partials, no API calls — same reasoning as Casper's equivalent: 500 errors usually happen when the server is struggling, so we don't want the error page itself to compound the issue. Uses Ghost's raw{{message}}since 500-class errors are technical/server-side and not really i18n-able. Also surfaces theerrorDetailsblock in dev mode so theme errors are visible.Translations
The 404 message is wrapped in
{{t "Page not found"}}to be properly translatable (vs{{message}}which outputs Ghost's hardcoded English string). This adds one new key:"Page not found"— added to all 14 locale files (context.json+ 13 languages)"Page introuvable"Existing keys reused (no addition needed):
"Go to the front page →"(button)"Recent posts"(suggestions section title)"Theme errors"(dev mode in error.hbs)Files
error.hbs,error-404.hbsNo existing template is modified.
Browser support
Standard HTML/CSS, no JavaScript, no modern API. Works everywhere.
Note about gscan
Running
npx gscan .on this branch reports one error aboutpartials/components/footer.hbs— this is a pre-existing issue onmain, unrelated to this PR. The two new templates pass Handlebars parsing without issue.