← back to Nineoh Guide
episode detail: prev/next adjacent-episode nav (row-comparison, crosses season boundaries) — deeper internal linking + crawl depth for SEO
7a343d78c5c69a36027fc29ad13bb1f727564176 · 2026-07-25 13:15:28 -0700 · Steve
Files touched
M apps/web/app/episodes/[season]/[episode]/page.tsx
Diff
commit 7a343d78c5c69a36027fc29ad13bb1f727564176
Author: Steve <steve@designerwallcoverings.com>
Date: Sat Jul 25 13:15:28 2026 -0700
episode detail: prev/next adjacent-episode nav (row-comparison, crosses season boundaries) — deeper internal linking + crawl depth for SEO
---
apps/web/app/episodes/[season]/[episode]/page.tsx | 51 +++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/apps/web/app/episodes/[season]/[episode]/page.tsx b/apps/web/app/episodes/[season]/[episode]/page.tsx
index c4774c8..d7f781c 100644
--- a/apps/web/app/episodes/[season]/[episode]/page.tsx
+++ b/apps/web/app/episodes/[season]/[episode]/page.tsx
@@ -21,6 +21,26 @@ async function getEpisode(season: number, episode: number) {
}
}
+async function getAdjacent(season: number, episode: number) {
+ try {
+ const next = await pool.query(
+ `select season_number, episode_number, title from episodes
+ where (season_number, episode_number) > ($1,$2)
+ order by season_number, episode_number limit 1`,
+ [season, episode]
+ );
+ const prev = await pool.query(
+ `select season_number, episode_number, title from episodes
+ where (season_number, episode_number) < ($1,$2)
+ order by season_number desc, episode_number desc limit 1`,
+ [season, episode]
+ );
+ return { prev: prev.rows[0] ?? null, next: next.rows[0] ?? null };
+ } catch {
+ return { prev: null, next: null };
+ }
+}
+
export async function generateMetadata({
params,
}: {
@@ -49,6 +69,7 @@ export default async function EpisodePage({
const { season, episode } = await params;
const ep = await getEpisode(Number(season), Number(episode));
if (!ep) notFound();
+ const { prev, next } = await getAdjacent(ep.season_number, ep.episode_number);
const jsonLd = {
"@context": "https://schema.org",
@@ -96,6 +117,36 @@ export default async function EpisodePage({
) : (
<p style={{ color: "var(--muted)" }}>An original recap for this episode is coming soon.</p>
)}
+
+ <nav
+ style={{
+ display: "flex",
+ justifyContent: "space-between",
+ gap: 12,
+ marginTop: 30,
+ paddingTop: 16,
+ borderTop: "1px solid var(--line)",
+ fontSize: 13,
+ }}
+ >
+ {prev ? (
+ <a href={`/episodes/${prev.season_number}/${prev.episode_number}`}>
+ ← S{prev.season_number}E{prev.episode_number} · {prev.title}
+ </a>
+ ) : (
+ <span />
+ )}
+ {next ? (
+ <a
+ href={`/episodes/${next.season_number}/${next.episode_number}`}
+ style={{ textAlign: "right", marginLeft: "auto" }}
+ >
+ S{next.season_number}E{next.episode_number} · {next.title} →
+ </a>
+ ) : (
+ <span />
+ )}
+ </nav>
</article>
);
}
← 99ec1b5 design-consistency pass: interior pages (episodes/cast/searc
·
back to Nineoh Guide
·
2026 graphics: Photoshop-baked grainy ombre hero (413KB jpg) 3b392c3 →