Skip to content

Commit f1d7827

Browse files
dani-polaniclaude
andcommitted
feat(seo): related-examples cross-links + custom 404 page
- H4: "More examples" section on every example page (6 sibling cards in ring order + "Browse all examples"); loader returns related list. Connects all 19 example pages into a navigable cluster for crawl distribution and onward navigation. - M13: custom src/routes/+error.svelte — branded 404/error page with status, message, links to home/examples/api, and noindex. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d4fc682 commit f1d7827

4 files changed

Lines changed: 116 additions & 9 deletions

File tree

bitext/src/routes/+error.svelte

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<script lang="ts">
2+
import { page } from '$app/state';
3+
import { resolve } from '$app/paths';
4+
import SiteFooter from '$lib/components/layout/SiteFooter.svelte';
5+
6+
const isNotFound = $derived(page.status === 404);
7+
const heading = $derived(isNotFound ? 'Page not found' : 'Something went wrong');
8+
const message = $derived(
9+
isNotFound
10+
? 'The page you were looking for does not exist or may have moved.'
11+
: (page.error?.message ?? 'An unexpected error occurred.')
12+
);
13+
14+
const linkClass =
15+
'font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300';
16+
const ctaClass =
17+
'inline-flex items-center gap-1 rounded-none border border-primary-600 bg-primary-600 px-4 py-2 text-sm font-medium text-white no-underline shadow-sm hover:bg-primary-700 dark:border-primary-500 dark:bg-primary-600 dark:hover:bg-primary-500';
18+
</script>
19+
20+
<svelte:head>
21+
<title>{page.status} · Word Aligner</title>
22+
<meta name="robots" content="noindex" />
23+
</svelte:head>
24+
25+
<main
26+
class="mx-auto flex w-full max-w-3xl min-w-0 flex-col px-4 pt-10 pb-16 text-gray-700 sm:px-6 md:pt-16 md:pb-20 dark:text-gray-300"
27+
>
28+
<p class="font-heading text-6xl font-bold text-gray-300 dark:text-gray-600">{page.status}</p>
29+
<h1 class="font-heading mt-4 text-3xl font-semibold text-gray-900 sm:text-4xl dark:text-white">
30+
{heading}
31+
</h1>
32+
<p class="mt-4 max-w-prose text-base leading-relaxed">{message}</p>
33+
34+
<div class="mt-8 flex flex-wrap items-center gap-x-4 gap-y-3">
35+
<a href={resolve('/')} class={ctaClass}>Open Word Aligner</a>
36+
<a href={resolve('/examples')} class="{linkClass} text-sm">Browse examples</a>
37+
<a href={resolve('/api')} class="{linkClass} text-sm">API docs</a>
38+
</div>
39+
40+
<SiteFooter />
41+
</main>

bitext/src/routes/examples/[slug]/+page.svelte

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,52 @@
128128
<a href="{resolve('/')}?example={exampleSlug}" class={ctaClass}> Open in Editor </a>
129129
</div>
130130

131+
{#if data.related.length > 0}
132+
<section
133+
class="mt-12 border-t border-gray-200 pt-8 dark:border-gray-700"
134+
aria-label="More examples"
135+
>
136+
<h2 class="font-heading m-0 text-xl font-semibold text-gray-900 dark:text-white">
137+
More examples
138+
</h2>
139+
<ul class="m-0 mt-5 grid list-none grid-cols-1 gap-5 p-0 sm:grid-cols-2">
140+
{#each data.related as rel (rel.slug)}
141+
<li>
142+
<article
143+
class="flex h-full flex-col overflow-hidden rounded-md border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800/40"
144+
>
145+
<a
146+
href={resolve(`/examples/${rel.slug}`)}
147+
class="block overflow-hidden border-b border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900/40"
148+
>
149+
<img
150+
src={rel.previewImageUrl}
151+
alt={rel.imageAlt}
152+
width={960}
153+
height={540}
154+
loading="lazy"
155+
decoding="async"
156+
class="w-full bg-white object-contain object-center dark:bg-gray-900/40"
157+
/>
158+
</a>
159+
<h3 class="m-0 p-4 text-base leading-snug font-semibold">
160+
<a
161+
href={resolve(`/examples/${rel.slug}`)}
162+
class="text-gray-900 no-underline hover:underline dark:text-white"
163+
>
164+
{rel.title}
165+
</a>
166+
</h3>
167+
</article>
168+
</li>
169+
{/each}
170+
</ul>
171+
<p class="mt-5 mb-0">
172+
<a href={resolve('/examples')} class="{linkClass} text-sm">Browse all examples →</a>
173+
</p>
174+
</section>
175+
{/if}
176+
131177
<div class="mt-10">
132178
<PartnerBannerById partnerId={data.partnerId} />
133179
</div>

bitext/src/routes/examples/[slug]/+page.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,31 @@ export function entries() {
1313
return GALLERY_EXAMPLES.map((entry) => ({ slug: entry.slug }));
1414
}
1515

16+
/** Up to 6 sibling examples (ring order) for the "More examples" cross-links. */
17+
function relatedExamples(slug: string) {
18+
const start = GALLERY_EXAMPLES.findIndex((e) => e.slug === slug);
19+
const out: { slug: string; title: string; previewImageUrl: string; imageAlt: string }[] = [];
20+
for (let i = 1; i <= 6; i++) {
21+
const e = GALLERY_EXAMPLES[(start + i) % GALLERY_EXAMPLES.length];
22+
if (e.slug === slug) continue;
23+
out.push({
24+
slug: e.slug,
25+
title: e.title,
26+
previewImageUrl: galleryPreviewImageFor(e),
27+
imageAlt: e.imageAlt
28+
});
29+
}
30+
return out;
31+
}
32+
1633
export const load: PageLoad = ({ params }) => {
1734
const entry = findGalleryBySlug(params.slug);
1835
if (!entry) error(404, 'Example not found');
1936
return {
2037
entry,
2138
previewImageUrl: galleryPreviewImageFor(entry),
2239
exampleSlug: entry.slug,
23-
partnerId: getExamplePagePartnerId(entry.slug)
40+
partnerId: getExamplePagePartnerId(entry.slug),
41+
related: relatedExamples(entry.slug)
2442
};
2543
};

docs/seo-plan.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ All pages missing `<meta property="og:site_name" content="Word Aligner">`. 5-min
108108

109109
**Done:** added `<meta property="og:site_name" content={SITE_NAME} />` to the layout `<svelte:head>`.
110110

111-
### H4 — Add cross-navigation to example pages
112-
Each of 19 example pages is a dead-end. Add:
113-
- 4–6 "Related examples" card links at the bottom
114-
- Visible breadcrumb nav: Word Aligner Examples[Page Title]
111+
### H4 — Add cross-navigation to example pages
112+
Each of 19 example pages was a dead-end.
113+
114+
**Done:** added a "More examples" section to every example page — a 6-card grid of sibling examples (ring order: the 6 following the current slug, wrapping) + a "Browse all examples" link. The loader (`+page.ts`) returns the `related` list. This connects all 19 pages into a navigable cluster (every page links to 6 others + the index), distributing crawl/PageRank and giving search visitors a path onward. Visible breadcrumb nav (` Aligner · Examples`) already existed; matching `BreadcrumbList` schema was added in H1.
115115

116116
### H5 — Move partner blocks below primary content on example pages ✅ ALREADY FINE
117117
Re-checked the actual `examples/[slug]/+page.svelte`: the partner banner is already the **last** element (after body copy, figure, and the "Open in Editor" CTA). The content agents' "card before content" claim conflated this with the **homepage mobile** layout, where the Preply card does sit above the editor — that's tracked under **M9**, not here. No change needed on example pages.
@@ -218,8 +218,10 @@ Domain ~2 months old, zero third-party backlinks (expected). First moves:
218218
4. **LINGUIST List** software directory submission
219219
5. **Public APIs GitHub repo** (under "Education" category) — `github.com/public-apis/public-apis`
220220

221-
### M13 — Custom 404 page (found outside the audit)
222-
The current 404 is bare "404 Not Found" — no branding, no navigation, dead end for users and crawlers landing on a stale/bad URL. Add a SvelteKit `src/routes/+error.svelte`: branded layout, a short friendly message, and links back to home / examples / API. Helps recover mistyped or delisted URLs and keeps the experience consistent. Low effort, low risk.
221+
### M13 — Custom 404 page (found outside the audit) ✅
222+
The 404 was bare "404 Not Found" — no branding, no navigation.
223+
224+
**Done:** added `src/routes/+error.svelte` — branded layout (status, friendly heading/message), CTA "Open Word Aligner" + links to examples and API docs, and `noindex`. Renders inside the existing layout (theme/fonts) for all error statuses (404 and others).
223225

224226
---
225227

@@ -281,11 +283,11 @@ The current 404 is bare "404 Not Found" — no branding, no navigation, dead end
281283
- [ ] M3 — Self-host Space Grotesk or add font preload
282284

283285
**Week 2–3**
286+
- [x] H4 — Cross-navigation "Related examples" on all example pages
287+
- [x] M13 — Custom 404 page (`+error.svelte`)
284288
- [ ] C2 — Expand remaining 14 example pages
285-
- [ ] H4 — Cross-navigation "Related examples" on all example pages
286289
- [ ] M9 — Fix mobile layout (affiliate card ordering, tooltip toggle)
287290
- [ ] H6 — Fix touch targets in alignment editor
288-
- [ ] M13 — Custom 404 page (`+error.svelte`)
289291

290292
**Week 4+**
291293
- [ ] M2 — Expand homepage guide sections + convert headings to questions

0 commit comments

Comments
 (0)