- newspaper-style masthead (stats | "Unique" | About/Blog) on every page with per-page italic subheader + last-updated date on listing pages; sitewide 42rem reading width - pinterest home (hero + masonry, "load more"); old list view at /index3/ - new pages: /about, /blog, /legal, /find/, /finds/, custom 404 - footer collection links now clickable; "founded" stat replaces "updated" in the masthead - centralised SEO component with per-page json-ld (Review, BlogPosting, Product, Article, CollectionPage, ItemList, BreadcrumbList, etc.); @astrojs/sitemap + @astrojs/rss; robots.txt; promoted finds canonical to their review - memoize getSiteStats() so the masthead/footer counts don't multiply per-page build cost - 25 new daily-finds captures
123 lines
6.2 KiB
Markdown
123 lines
6.2 KiB
Markdown
# Unique
|
|
|
|
A small blog about delightful, unique things — products, apps, phenomena, oddities.
|
|
|
|
Built with [Astro](https://astro.build) + MDX.
|
|
|
|
## Key features
|
|
|
|
- **Four-collection pipeline**: `find` (captured items, hidden from indexes), `finds` (editorial superposts that reference find slugs in a grid), `reviews` (full-length, sometimes graduated from a find), `posts` (generic essays)
|
|
- **Daily capture skills** under `.claude/skills/`: `daily-finds` (sweeps every source in `sources.json`, writes one find file per qualifying item — capture-only, no editorial selection); `build-finds` (drafts a thematic superpost from the find pool); `build-review` (graduates a find to a full review, linking the two via `fromFind` / `promotedTo`)
|
|
- **Sources catalog** at `sources.json` with per-source fetch strategy (`webfetch` / `curl` / `skip`) and the `/sources` page rendering a 3-column card stream with a "Recent finds" sub-list per card
|
|
- **Strict find visibility**: find items are reachable only via the superposts that reference them, the `/sources` cross-reference, or direct URL — they don't crowd the home feed, tag pages, or category pages
|
|
- **Backlinks** computed at build time: each find page lists every superpost that references it; promoted finds show a forward link to the review; reviews show a "← First surfaced…" backlink to their originating find
|
|
- **Newspaper-style masthead** on every page: live entry-count + last-updated stats on the left, large serif "Unique" wordmark centered between thin rules, About/Blog nav on the right; per-page italic subheader ("A small log of delightful things.") with last-updated date on listing pages
|
|
- **Pinterest-style home page**: hero card for the latest review plus a 3-column masonry grid of remaining reviews with a "Load more" reveal; old plain-list home preserved at `/index3/`
|
|
- **Unified chronological feed** at `/index3/` across reviews + finds (superposts) + posts
|
|
- **SEO-friendly site footer** with Browse / Collections (live counts) / About link columns and a centered legal line; consistent 76rem page width across the entire site (article prose constrained to a comfortable reading measure inside the wider layout)
|
|
- Reviews render as two-line index entries: bold name + muted subtitle (`Cotypist` / *Autocomplete that lives everywhere on your Mac*)
|
|
- Auto-generated `/categories/<name>/` and `/tags/<name>/` listing pages — finds excluded by design
|
|
- **Amazon affiliate handling** in `src/lib/affiliate.ts`: outbound links to Amazon (`amazon.com`, `amzn.to`, `a.co`, `smile.amazon.com`) get the configured tracking ID appended automatically; the link gets `rel="sponsored"`, an "Affiliate" badge appears next to it, and a one-line disclosure is shown beneath. Add additional marketplace IDs (e.g. `amazon.co.uk`) to the `AMAZON_TAGS` map when needed
|
|
- **SEO surface**: single `<SEO>` component owning canonical, Open Graph, Twitter Card, and JSON-LD; per-page structured data (`Review`, `BlogPosting`, `Product`, `Article`, `CollectionPage`, `ItemList`, `BreadcrumbList`, `WebSite`, `Organization`); auto-generated `sitemap-index.xml` (excludes `/grid/` and `/index3/`); RSS feed at `/rss.xml`; `robots.txt`; static OG image fallbacks under `/public/og/`; promoted finds canonical to their review
|
|
- Custom 404 page with recent-reviews and navigation fallback
|
|
- Light/dark mode via `prefers-color-scheme`
|
|
- No JavaScript shipped to the client
|
|
- Static build, deployable anywhere
|
|
|
|
## Local development
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # local dev server
|
|
npm run build # type-check + static build to ./dist
|
|
npm run preview # serve the built site
|
|
```
|
|
|
|
## Adding a review
|
|
|
|
Create an MDX file under `src/content/reviews/<slug>.mdx`:
|
|
|
|
```mdx
|
|
---
|
|
name: "Cotypist"
|
|
subtitle: "Autocomplete that lives everywhere on your Mac"
|
|
date: 2026-05-03
|
|
category: writing
|
|
tags: [macos, productivity]
|
|
link: https://cotypist.app/ # optional product URL
|
|
linkText: "Visit in App Store" # optional; defaults to the link's hostname (sans `www.`)
|
|
description: "Optional <meta> override; defaults to subtitle."
|
|
---
|
|
|
|
Body in markdown / MDX. You can import and use components here.
|
|
```
|
|
|
|
The slug becomes the URL at `/reviews/<slug>/`.
|
|
|
|
## Media
|
|
|
|
Visuals for a review live at `src/assets/reviews/<slug>/`, mirroring the slug. Reference them from MDX with relative paths so Astro's image pipeline picks them up (responsive variants, lazy loading, modern formats):
|
|
|
|
```mdx
|
|

|
|
```
|
|
|
|
Prefer real screenshots, GIFs, or short clips from the app's own site (downloaded locally — don't hotlink). Pair with a short italic caption that names what's being shown. Visuals should illustrate the virtue of the product, not decorate the page.
|
|
|
|
## Adding a post (essay)
|
|
|
|
Create an MDX file under `src/content/posts/<slug>.mdx`:
|
|
|
|
```mdx
|
|
---
|
|
title: "Some essay title"
|
|
date: 2026-05-10
|
|
category: writing
|
|
tags: [meta, opinion]
|
|
description: "Optional <meta> tag override."
|
|
---
|
|
|
|
Body in markdown / MDX.
|
|
```
|
|
|
|
The slug becomes the URL at `/posts/<slug>/`.
|
|
|
|
## Project layout
|
|
|
|
```
|
|
src/
|
|
content.config.ts # collection schemas (reviews, posts)
|
|
assets/
|
|
reviews/<slug>/ # hero images and screenshots per review
|
|
content/
|
|
reviews/*.mdx # product reviews
|
|
posts/*.mdx # essays (empty for now)
|
|
lib/
|
|
entries.ts # getAllEntries() — unified feed across collections
|
|
components/
|
|
EntryList.astro # shared two-shape entry list
|
|
layouts/
|
|
BaseLayout.astro
|
|
pages/
|
|
index.astro # Pinterest-style home (hero + masonry)
|
|
index3.astro # plain chronological list view
|
|
about.astro # About page
|
|
blog.astro # /posts index (essays)
|
|
grid.astro # full grid of every review
|
|
sources.astro # source catalog
|
|
reviews/[...slug].astro
|
|
posts/[...slug].astro
|
|
categories/[category].astro
|
|
tags/[tag].astro
|
|
public/
|
|
favicon.svg
|
|
```
|
|
|
|
## Adding a new content type
|
|
|
|
1. Define the collection in `src/content.config.ts`.
|
|
2. Extend the `Entry` union and `getAllEntries()` in `src/lib/entries.ts` so the new type joins the merged feed.
|
|
3. Add a detail route at `src/pages/<type>/[...slug].astro`.
|
|
|
|
Listing pages (index, categories, tags) need no changes — they consume `getAllEntries()`.
|