DEV Community

LION ZHANL
LION ZHANL

Posted on

Building a Search-Friendly Archive for a Daily Puzzle Site with Next.js

Daily puzzle sites have an unusual content problem: the homepage changes every day, but search engines and players still need stable pages for yesterday, last month, and older practice rounds.

I ran into this while building Pinpoint Answer Today, an independent companion site for a daily word-connection puzzle. A single “today” page was easy. Turning hundreds of puzzle records into a useful, crawlable archive without publishing thin or unfinished pages was the real engineering work.

This post covers the structure I ended up using with the Next.js App Router.

1. Treat each puzzle as reviewed content, not a generated URL

My first version of the content model only needed a number, five clues, and an answer. That was enough to render a page, but not enough to make the page useful.

The model eventually needed editorial state too:

type Puzzle = {
  number: number
  slug: string
  publishedAt: string
  clues: [string, string, string, string, string]
  answer: string
  explanation: string
  clueNotes: string[]
  reviewedAt?: string
  indexable: boolean
}
Enter fullscreen mode Exit fullscreen mode

The important field is not the answer. It is indexable.

A route can exist for preview or internal review without being eligible for the sitemap. That separation prevents an automated import from turning incomplete records into hundreds of thin public pages.

2. Give every edition one permanent canonical URL

Daily sites are tempted to use URLs such as /today and replace the content every 24 hours. That is convenient for visitors but poor for an archive because the same URL keeps changing meaning.

I use two layers instead:

  • A current landing page that always points to the newest reviewed edition
  • A permanent URL for every historical puzzle

The permanent page owns its title, canonical URL, date, clues, explanation, and navigation to adjacent editions.

In the App Router, the metadata can be generated from the same reviewed record used by the page:

export async function generateMetadata({ params }: PageProps) {
  const { slug } = await params
  const puzzle = await getPuzzle(slug)

  return {
    title: `LinkedIn Pinpoint ${puzzle.number}: ${puzzle.clues.join(", ")}`,
    description: `Clues, answer, and explanation for Pinpoint ${puzzle.number}.`,
    alternates: {
      canonical: `/linkedin-pinpoint-answer/${puzzle.slug}`,
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

The exact title format matters less than consistency. A stable pattern makes duplicate detection, sitemap auditing, and editorial review much easier.

3. Keep incomplete pages out of the index

Rendering a route and asking search engines to index it are separate decisions.

For incomplete historical records I use three safeguards:

  1. Return noindex metadata.
  2. Exclude the URL from the sitemap.
  3. Avoid linking to it from the public archive.

Only after the explanation, clue notes, date, and review status are complete does the page enter the crawlable graph.

This editorial gate is slower than publishing everything at once, but it produces an archive where every indexed URL has a reason to exist.

4. Generate the sitemap from the editorial state

A sitemap should describe the canonical, approved site — not every row that happens to be in a data file.

The core rule is small:

const indexedPuzzles = puzzles.filter(
  (puzzle) => puzzle.indexable && puzzle.reviewedAt
)

const entries = indexedPuzzles.map((puzzle) => ({
  url: `https://example.com/linkedin-pinpoint-answer/${puzzle.slug}`,
  lastModified: puzzle.reviewedAt,
}))
Enter fullscreen mode Exit fullscreen mode

I also include evergreen pages such as the archive, topic index, methodology, and practice mode.

The daily update can then refresh the homepage, archive, feed, and sitemap together. That is much safer than remembering four unrelated manual steps.

5. Progressive disclosure improves both utility and content quality

An answer page has two competing users:

  • The player who wants a small hint without a spoiler
  • The visitor who already finished and wants the full explanation

Dumping the answer at the top serves only the second group.

The interface now reveals clues progressively and keeps the final answer behind an explicit action. The page still contains a complete explanation for people who want to review it, but the experience does not ruin the puzzle immediately.

The same content model powers a separate practice mode. It selects from 300 reviewed rounds, starts with limited information, and reveals more only when requested.

This creates an evergreen use for historical data instead of treating the archive as a pile of expired answer pages.

6. Structured data must match visible content

It is easy to generate JSON-LD for every possible field. The harder rule is that structured data should describe what the visitor can actually see.

For an article page, I generate article metadata from the same title, description, author, publication date, and canonical URL rendered on the page. Breadcrumb entries use the real navigation hierarchy. FAQ markup is added only when the questions and answers are visibly present.

One source of truth prevents schema drift.

7. Separate the fast daily workflow from the slower archive workflow

The newest puzzle is time-sensitive. Historical quality work is not.

I keep them as two different editorial paths:

  • Daily path: verify the puzzle number, clues, answer, and explanation; update discovery surfaces.
  • Archive path: review a small batch of older pages, improve explanations, and only then enable indexing.

Trying to repair the entire archive during the daily update made both jobs unreliable. Separating them keeps the newest page timely without lowering the standard for historical pages.

What I would do differently

If I started again, I would add editorial status on day one. Retrofitting review state after routes already existed was much harder than defining the gate early.

I would also design the practice experience alongside the archive. Historical data becomes far more valuable when visitors can interact with it instead of only reading it.

The live implementation is here:

Pinpoint Answer Today

Practice mode with 300 reviewed rounds

This is an independent project and is not affiliated with or endorsed by LinkedIn.

The broader lesson applies beyond puzzles: if content changes every day, build the permanent archive and editorial gate before scale makes inconsistency expensive.

Top comments (0)