site: tag pages include finds; find-only tags get pages

Tag pages are now generated from the union of entry and find tags —
1,179 tags that existed only on finds (most tag links on find pages)
previously 404'd. Tagged finds render in a compact secondary "Finds"
list below the entry grid, keeping the editorial tier on top. Find-only
tag pages with fewer than 3 finds are noindexed until they fill out.
Tag feeds stay entries-only; the RSS pill and autodiscovery link only
render where a feed exists. Category pages unchanged (finds have no
category).

Claude-Session: https://claude.ai/code/session_01WZaczDJjL3xZ3u5spsN5AL
This commit is contained in:
2026-07-12 19:05:46 -04:00
parent ccfee1c8b5
commit ff4c6959a5
7 changed files with 74 additions and 17 deletions
+8 -3
View File
@@ -1,8 +1,13 @@
---
import type { Entry } from '../lib/entries';
// Structural subset of lib/entries' Entry so callers can also feed mapped
// find items (tag pages) without pretending they're full entries.
interface Props {
entries: Entry[];
entries: Array<{
href: string;
date: Date;
primary: string;
secondary?: string;
}>;
}
const { entries } = Astro.props;
+1
View File
@@ -31,6 +31,7 @@ const breadcrumbs = buildBreadcrumbs([
title="Feeds"
description={description}
subheader="Take Unique with you."
ogImage={ogImage}
jsonLd={[collectionPage, breadcrumbs]}
>
<h1>Feeds</h1>
+60 -10
View File
@@ -1,7 +1,9 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import EntryGrid from '../../components/EntryGrid.astro';
import EntryList from '../../components/EntryList.astro';
import { getAllEntries } from '../../lib/entries';
import { getAllFinds } from '../../lib/find-items';
import {
buildOgImage,
buildBreadcrumbs,
@@ -11,29 +13,58 @@ import {
export async function getStaticPaths() {
const entries = await getAllEntries();
const tags = [...new Set(entries.flatMap((e) => e.tags))];
return tags.map((tag) => ({
const finds = await getAllFinds();
const tags = new Set([
...entries.flatMap((e) => e.tags),
...finds.flatMap((f) => f.data.tags),
]);
return [...tags].map((tag) => ({
params: { tag },
props: {
tag,
entries: entries.filter((e) => e.tags.includes(tag)),
finds: finds.filter((f) => f.data.tags.includes(tag)),
},
}));
}
const { tag, entries } = Astro.props;
const lastUpdated = entries[0]?.date;
const count = entries.length;
const topNames = entries.slice(0, 3).map((e) => e.primary).join(', ');
const { tag, entries, finds } = Astro.props;
const findRows = finds.map((f) => ({
href: `/find/${f.id}/`,
date: f.data.date,
primary: f.data.name,
secondary: f.data.subtitle ?? f.data.description,
}));
const count = entries.length + finds.length;
const lastUpdated = [entries[0]?.date, finds[0]?.data.date]
.filter((d): d is Date => Boolean(d))
.sort((a, b) => b.valueOf() - a.valueOf())[0];
const topNames = [...entries.map((e) => e.primary), ...finds.map((f) => f.data.name)]
.slice(0, 3)
.join(', ');
const description =
topNames.length > 0
? `All ${count} entries tagged #${tag}: ${topNames}${count > 3 ? '…' : '.'}`
: `All entries tagged #${tag}.`;
// Find-only tags with a couple of items are thin; keep them browsable but
// out of the index until they accumulate content.
const noindex = entries.length === 0 && finds.length < 3;
// A scoped feed only exists when the tag has entries (feeds mirror the
// editorial surface: reviews + posts + bundles).
const feedHref = entries.length > 0 ? `/tags/${tag}/rss.xml` : undefined;
const ogImage = buildOgImage(undefined, 'site', `#${tag} on Unique`);
const itemList = buildItemList(
entries.map((e) => ({ url: e.href, name: e.primary })),
[
...entries.map((e) => ({ url: e.href, name: e.primary })),
...finds.map((f) => ({ url: `/find/${f.id}/`, name: f.data.name })),
],
`#${tag} on Unique`
);
@@ -59,15 +90,34 @@ const breadcrumbs = buildBreadcrumbs([
subheader={`Tagged: ${tag}.`}
lastUpdated={lastUpdated}
ogImage={ogImage}
noindex={noindex}
jsonLd={[collectionPage, itemList, breadcrumbs]}
feeds={[{ title: `Unique — #${tag} (RSS)`, href: `/tags/${tag}/rss.xml` }]}
feedHref={`/tags/${tag}/rss.xml`}
feeds={feedHref ? [{ title: `Unique — #${tag} (RSS)`, href: feedHref }] : undefined}
feedHref={feedHref}
wide
>
<EntryGrid entries={entries} />
{entries.length > 0 && <EntryGrid entries={entries} />}
{finds.length > 0 && (
<section class="tag-finds">
<h2>Finds</h2>
<EntryList entries={findRows} />
</section>
)}
<p class="back"><a href="/">← back</a></p>
<style>
.tag-finds {
margin-top: 2.5rem;
}
.tag-finds:first-child {
margin-top: 0;
}
.tag-finds h2 {
margin: 0 0 0.25rem;
font-size: 1.15rem;
}
.back { margin-top: 2rem; font-size: 0.9rem; }
</style>
</BaseLayout>