Photo Metadata
Jacky's Photography keeps generated camera metadata in the builder manifest and keeps human-written copy in photo-descriptions.json. The sidecar file is merged into the manifest during pnpm run build:manifest, so editorial descriptions can improve search, alt text, detail pages, and social sharing without hand-editing generated manifest output.
Sidecar Shape
photo-descriptions.json is versioned JSON at the repository root. Each entry is matched by storage key, normalized without a leading photos/ prefix.
{
"version": 1,
"photos": [
{
"key": "随手/20260508120000.jpg",
"title": "Window Light",
"descriptions": {
"zh-CN": "一束光落在窗边,给日常留下安静的边缘。",
"en": "A quiet window-lit frame from an ordinary afternoon."
},
"tags": ["window", "light", "daily", "color"],
"aiContext": {
"currentTitle": "",
"dateTaken": "2026-05-08T12:00:00.000Z",
"camera": "FUJIFILM X-T5",
"lens": "FUJIFILM XF35mmF1.4 R",
"categoryTags": ["随手"]
}
}
]
}
Field behavior:
keyidentifies the photo by manifests3Keyafter path normalization.titlereplaces the generated title when it is non-empty.descriptions.zh-CNanddescriptions.enare the maintained localized descriptions.tagsare merged into generated tags while preserving order and removing duplicates.aiContextis helper context from the current manifest and is not merged into the public manifest.
The current editorial convention is to keep manual tags short and focused, usually no more than four tags per photo.
Sync Workflow
Run the builder first so apps/web/src/data/photos-manifest.json reflects the current photo set:
pnpm run build:manifest
Then sync the sidecar file:
pnpm run photos:descriptions:sync
The sync script reads apps/web/src/data/photos-manifest.json, creates missing sidecar entries, preserves existing title, descriptions, and tags, and refreshes aiContext from the latest manifest data. packages/data/src/photos-manifest.json is only a symlink to this generated file, so do not edit either manifest path by hand.
Use pruning only when old sidecar entries should be removed:
pnpm run photos:descriptions:sync -- --prune
After editing titles, descriptions, or tags, run the builder again:
pnpm run build:manifest
Manifest Merge
builder.config.ts registers plugins/builder/photo-descriptions.ts. The plugin runs before the manifest is saved:
- Missing
photo-descriptions.jsonis allowed; the build skips manual metadata. - Sidecar keys are normalized by removing leading slashes and an optional
photos/prefix. - Matching entries can update
title,descriptions, fallbackdescription, andtags. descriptionfalls back todescriptions["zh-CN"], thendescriptions.en.- Manual tags are appended after existing tags and deduplicated.
The public manifest therefore exposes both:
interface PhotoInfo {
description: string
descriptions?: Record<string, string>
}
description remains the compatibility field. descriptions is used by localized UI and SEO behavior.
Frontend Usage
The web app resolves descriptions through apps/web/src/lib/photo-description.ts:
- Chinese locales prefer the exact locale, then
zh-CN, thenen. - English locales prefer the exact locale, then
en, thenzh-CN. - Other locales try the exact locale, base language,
en, thenzh-CN. - Alt text falls back from localized description to title, then photo ID.
Current consumers include:
- masonry thumbnails and manifest cards through localized alt text
- command palette photo search across every available description
- photo detail route meta via
usePageMeta - map markers and cluster thumbnails through localized alt text
- static per-photo build output through
photo-page-meta
SEO Output
During production builds, apps/web/plugins/vite/photo-page-meta.ts reads the manifest and writes an HTML file for each photo at:
photos/<photo-id>/index.html
Each generated page keeps the SPA shell but replaces page metadata with photo-specific values:
<title>meta[name="description"]link[rel="canonical"]og:type,og:url,og:title,og:description,og:imagetwitter:url,twitter:title,twitter:description,twitter:image
The sitemap plugin also includes photo detail URLs, so the generated metadata is available to crawlers and social link unfurlers before the client app hydrates.
| Created At | |
| Last Modified |