Skip to content

Commit 067215b

Browse files
docs(puzzle-page): document SEO description/OG/Twitter wiring + future risks
- Updated "How to configure/add a new puzzle" step 1 to mention the new required `description` field on PuzzleConfig, with an explicit instruction to write real, distinct, human-quality copy per entry rather than a templated string. - Added a new "SEO: meta description, Open Graph, Twitter card" subsection describing exactly how render.puzzlePage.web.tsx derives <meta name="description">, og:title/og:description, and twitter:title/twitter:description from puzzleConfig.description and webTitle. - Added a new, clearly-flagged "SEO risks to revisit before shipping calendar/archive features" subsection under "Open questions / known limitations", covering (not implementing) two risks for future work: - Date-specific URLs (V1 calendar navigation, building on instance.puzzleDate) risking duplicate/thin indexable pages unless a canonical-vs-deliberate-indexing decision is made explicitly upfront. - Archive/pagination features (PuzzleConfig.hasArchive exists but is unused/unbuilt) risking poor search indexing if built carelessly, referencing a concrete existing example elsewhere on the Guardian site (the crossword archive/search listing currently indexed with a generic "Crossword | Page 2 of 1082" title and a garbled, auto-scraped listing-style description) as a cautionary precedent to avoid repeating. - Notes this isn't an active problem today (no archive/pagination UI exists yet) but should be raised as a design question at the start of that future work, not discovered after launch. No functional/code changes in this commit - documentation only. tsc --noEmit clean, full-repo eslint clean, full test suite passing (175 suites / 1272 tests). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 872b415 commit 067215b

1 file changed

Lines changed: 65 additions & 5 deletions

File tree

dotcom-rendering/docs/puzzle-page.md

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,43 @@ iframe-based slug:
118118

119119
1. Add a new key to `src/model/puzzles/puzzleConfigs.ts`'s `puzzleConfigs`
120120
record (`slug`, `puzzleGroup`, `iframe: { provider, urlTemplate }`,
121-
`shareEnabled`, `printEnabled`, `hasArchive`). If it's another
122-
AmuseLabs-hosted puzzle, reuse the `amuseLabsPuzzle(slug, puzzleGroup)`
123-
helper. `validatePuzzleConfigs` runs once at module load and throws
124-
immediately if the entry is malformed (mismatched `slug`, unknown
125-
`puzzleGroup`, or empty `iframe.provider`/`iframe.urlTemplate`).
121+
`shareEnabled`, `printEnabled`, `hasArchive`, `description`). If it's
122+
another AmuseLabs-hosted puzzle, reuse the
123+
`amuseLabsPuzzle(slug, puzzleGroup, description)` helper.
124+
`validatePuzzleConfigs` runs once at module load and throws immediately
125+
if the entry is malformed (mismatched `slug`, unknown `puzzleGroup`,
126+
empty `iframe.provider`/`iframe.urlTemplate`, or empty `description`).
127+
**Write real, distinct, human-quality copy for `description`** — it
128+
becomes the page's `<meta name="description">` and its derived Open
129+
Graph/Twitter description (see "SEO" below); don't copy-paste one
130+
template string across entries with only the slug swapped in.
126131
2. Nothing else changes on the DCR side: `PuzzlePageLayout.tsx`'s
127132
`PuzzlePageContent` unconditionally renders `PuzzleIframe` pointed at
128133
`resolveIframeUrl(puzzleConfig)` for every registry entry. The only thing
129134
needed from `frontend` is a request whose `slug` matches the new
130135
registry key exactly (see the `frontend` repo's `docs/puzzle-page.md`).
131136

137+
### SEO: meta description, Open Graph, Twitter card
138+
139+
Each `PuzzleConfig` entry carries a curated `description` (a short,
140+
genuinely-written meta description, distinct per puzzle — see step 1
141+
above). `render.puzzlePage.web.tsx` uses
142+
`puzzlePage.puzzleConfig.description` (the config already resolved by
143+
`handler.puzzlePage.web.ts`, not a fresh lookup) for three things:
144+
145+
- The page's `<meta name="description">` (previously hardcoded to `''`,
146+
which silently fell back to DCR's generic, site-wide description — a
147+
real SEO gap, since a generic/absent description risks Google or social
148+
previews auto-generating a snippet from page content instead of showing
149+
clean, curated copy).
150+
- `openGraphData: { 'og:title': webTitle, 'og:description': description }`.
151+
- `twitterData: { 'twitter:title': webTitle, 'twitter:description': description }`.
152+
153+
Puzzle Page has no separate source of Open Graph/Twitter copy (unlike
154+
Article, where `frontend` sends its own `openGraphData`/`twitterData`), so
155+
these are derived directly from `webTitle`/`description` rather than
156+
requiring bespoke copy per field.
157+
132158
### The `FEPuzzlePageType` request contract
133159

134160
`POST /PuzzlePage` validates the body against `FEPuzzlePageType`
@@ -282,3 +308,37 @@ darkMode: boolean } }` and the `?guardian-puzzle-context=<JSON>` query
282308
- **The Puzzles Hub (`src/layouts/PuzzlesLayout.tsx` and friends) is a
283309
separate, unrelated feature** (a directory/listing page) and is not
284310
documented in this file.
311+
312+
### SEO risks to revisit before shipping calendar/archive features
313+
314+
**Read this before adding date-specific URLs (V1 calendar navigation) or
315+
any archive/pagination UI to Puzzle Page.** No page in Puzzle Page today
316+
creates unbounded or paginated URLs (there is no archive UI yet, despite
317+
`PuzzleConfig.hasArchive` existing — see above), so this isn't an active
318+
problem yet. It becomes one the moment calendar or archive work begins, and
319+
should be raised as an explicit design question at the _start_ of that
320+
work, not discovered after launch.
321+
322+
- **Date-specific URLs risk creating duplicate/thin indexable pages.**
323+
Once `instance.puzzleDate` (or a real calendar UI) lets readers reach a
324+
specific past date's puzzle via a URL — whether a query param or a path
325+
segment — every such URL must either (a) carry a `canonical` pointing
326+
back to the puzzle's main/"today" URL, if individual dates aren't meant
327+
to be indexed separately, or (b) be a deliberate, explicit decision to
328+
index each date individually with genuinely distinct content/copy per
329+
date. This must be decided explicitly before shipping, not left as an
330+
accidental side effect of adding date-awareness to the URL.
331+
- **Archive/pagination features carry a known, real risk of poor search
332+
indexing if built carelessly.** A concrete, existing cautionary example
333+
elsewhere on the Guardian site: the crossword archive/search listing is
334+
currently indexed by Google with a generic, unhelpful title
335+
("Crossword | Page 2 of 1082") and a garbled, listing-style meta
336+
description auto-scraped from page content (a concatenated list of
337+
puzzle names) rather than a clean, curated one — a direct consequence of
338+
paginated listing pages being indexed individually without proper
339+
`canonical`/`noindex`/curated-metadata handling. Any future Puzzle Page
340+
archive feature must avoid this from the start: genuinely curated
341+
titles/descriptions per archive page (never auto-generated from a list of
342+
contents, the same principle behind `PuzzleConfig.description` above),
343+
and an explicit `canonical`/`noindex`/pagination-indexing strategy decided
344+
upfront — not defaulting to "index everything" and finding out later.

0 commit comments

Comments
 (0)