Skip to content

Add default error.hbs and error-404.hbs templates#145

Open
bst1n wants to merge 3 commits into
TryGhost:mainfrom
bst1n:add-default-error-pages
Open

Add default error.hbs and error-404.hbs templates#145
bst1n wants to merge 3 commits into
TryGhost:mainfrom
bst1n:add-default-error-pages

Conversation

@bst1n
Copy link
Copy Markdown

@bst1n bst1n commented May 24, 2026

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.hbs

Extends default.hbs so 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.hbs

Standalone (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 the errorDetails block 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)
  • English value defaults to the key itself (Source convention)
  • French translation included: "Page introuvable"
  • Other languages: empty string (waiting for contributions from native speakers)

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

  • New: error.hbs, error-404.hbs
  • Modified: 14 locale files (1 line each)

No 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 about partials/components/footer.hbs — this is a pre-existing issue on main, unrelated to this PR. The two new templates pass Handlebars parsing without issue.

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.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

Review Change Stack

Walkthrough

This 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 @custom.site_background_color). It also inserts the "Page not found" localization key into context.json and multiple locale files (fr.json includes a translation).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding two new error template files (error.hbs and error-404.hbs) to handle error pages.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, clearly explaining the problem, solution, implementation details, and files affected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 038f9d6 and b9d85f1.

📒 Files selected for processing (2)
  • error-404.hbs
  • error.hbs

Comment thread error.hbs
Comment on lines +19 to +27
<style>
:root {
--background-color: {{@custom.site_background_color}}
}
</style>
{{ghost_head}}
</head>
<body class="{{body_class}}">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
<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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread error.hbs

<section class="gh-content is-body">
<p>
<a href="{{@site.url}}" class="gh-button">{{t "Go to the front page →"}}</a>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

bst1n added 2 commits May 24, 2026 17:54
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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b9d85f1 and 968ad85.

📒 Files selected for processing (15)
  • error-404.hbs
  • locales/context.json
  • locales/de-CH.json
  • locales/de.json
  • locales/en.json
  • locales/fr.json
  • locales/ga.json
  • locales/gd.json
  • locales/nl.json
  • locales/pt-BR.json
  • locales/sv.json
  • locales/tr.json
  • locales/uk.json
  • locales/zh-Hant.json
  • locales/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

Comment thread error-404.hbs
Comment on lines +34 to +40
<main class="gh-main">
<div class="gh-feed">
{{#foreach suggested}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
</div>
</main>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant