feat(seo): structured data and share images per page, sitemap dates from git, every product in the changelog - #16
Merged
Merged
Conversation
…ap, all products in the changelog Structured data - The Enterprise SoftwareApplication is no longer injected on every page by Base.astro; it lives on /thinkwatch (and the home page) only. /license, /changelog and the docs carry WebPage, BreadcrumbList or TechArticle, the 404 page only the Organization. - src/lib/structured-data.ts describes each product once, for its own page and the home page: ThinkWatch Lite as a SoftwareApplication (operating systems, processors, version and installers of the latest release, install URL, screenshots, interface languages, feature list from the page copy, MIT, free) plus its SoftwareSourceCode; ThinkWatch Core as SoftwareSourceCode plus a SoftwareApplication for the twcore binary (Linux, macOS, Windows; version and binaries of the latest Core release); ThinkWatch Enterprise as before. Entities carry @ids and refer to the Organization, which gains the homebrew-tap in sameAs. - The WebSite entity is on the home page only, without the SearchAction: the docs never read ?q=, and Google no longer shows a sitelinks search box. - JSON-LD escapes "<" so no string can end its script element. Share images - One 1200x630 card per product and one for the site, rendered at build time by src/pages/og/[card].png.ts: the product name, the page's headline and the platforms. Text is drawn as outlines from the Geist fonts in node_modules (@fontsource/geist, fontkit), so the card no longer depends on the fonts of the machine that builds it; CI had been drawing the old one in DejaVu Sans. - Each page sets its own og:image and og:image:alt (in the page's language); docs pages are og:type article with published and modified times. No twitter:site: there is no ThinkWatch account on X. Indexing - The 404 page is noindex and has no canonical or hreflang links; its language choice leads to the home page instead of the missing /404/. - Sitemap <lastmod> is the last commit that touched the files a page is built from (src/lib/lastmod.ts), and for the changelog the newest release; without git history the dates are left out rather than set to the build time. The deploy workflow checks out the full history and passes GITHUB_TOKEN to the build. Changelog and feed - /changelog lists every release of the three products from the GitHub API (product, version, date, link to the release), filterable by product, with the Enterprise notes written for this site kept on their versions. When GitHub cannot be reached, the build uses src/data/releases.json (refreshed with `pnpm releases`). Both languages share ChangelogPage.astro. - The page and the RSS feed are titled "ThinkWatch release notes" and cover Enterprise, Lite and Core; feed items link to the GitHub release, or to the notes on this site. Also: per-screenshot alt text for the Lite page's features (both languages), the sitemap's change frequencies typed with ChangeFreqEnum. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deploy workflow only runs on main, so a change that breaks the build was first seen when it failed to deploy. The share images now fail the build when a headline no longer fits, which is worth catching in review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y their releases - The changelog keeps the anchors it had when it listed Enterprise alone (#v0.4.0): each Enterprise entry carries one next to its new #enterprise-v0.4.0 anchor. The four Enterprise items that were already in the feed keep their old link and guid (/changelog#v0.4.0), so feed readers do not show them again. - A page's <lastmod> also counts the latest release of the products it shows: Lite for /lite, Core for /core, Enterprise for /thinkwatch, and all three for the home page (the Lite download, and the structured data of every product) and for the changelog. The home page's sources now include the hero, the terminal, the console mockups and the Lite download button. - deploy.yml: the build job, which runs npm packages with GITHUB_TOKEN set, gets contents: read only; pages: write and id-token: write move to the deploy job. - og:image:alt leaves out labels the headline already names, so the Lite alt no longer lists the platforms twice. - The twcore description in the structured data has its own copy field instead of being cut out of the Core card title. - The breadcrumbs of the Enterprise guides no longer list /docs twice (Documentation and ThinkWatch Enterprise are the same page). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d by releases A release date alone does not say when a page last changed, so a build without history (a shallow clone) emits no <lastmod> at all, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Technical SEO for thinkwat.ch now that ThinkWatch Lite ships on macOS, Windows and Linux and connects to a remote core. This PR does not change page prose; a later pass rewrites the copy.
Structured data
Base.astroinjected the EnterpriseSoftwareApplicationon every page withoutnoJsonLd(/thinkwatch, /changelog, 404). That default and thenoJsonLdswitch are gone./thinkwatchand the home page describe Enterprise./licenseand/changelogcarryWebPageandBreadcrumbList, the docs carryBreadcrumbListandTechArticle, and the 404 page has only theOrganization.src/lib/structured-data.tsbuilds the JSON-LD for each product's own page and for the home page, so the two agree:SoftwareApplicationwithoperatingSystem"macOS, Windows, Linux",processorRequirements, thesoftwareVersionand installerdownloadUrls of the latest release (read at build time, as for the download buttons),installUrl(…/lite/#install), the six screenshots with their captions,inLanguageen and zh-CN (the interface languages), afeatureListtaken from the page's feature titles, the MIT license and a price of 0. It also has aSoftwareSourceCodeentry.SoftwareSourceCode, plus aSoftwareApplicationfor the twcore binary. It lists Linux, macOS and Windows, and takes its version and binaries from the latest Core release, fetched the same way. Its description has its own field (meta.twcoreDescriptioninsrc/i18n/pages/core.ts) instead of being cut out of the page's card title.@ids. The source code points at the application throughtargetProduct, and author and publisher refer to theOrganization. TheOrganizationaddshomebrew-taptosameAs, and its description now calls the server product ThinkWatch Enterprise.WebSiteentity appears on the home page only. ItsSearchActionis removed: the docs never read?q=, and Google has retired the sitelinks search box.<, so a string can never close its<script>early.Share images (Open Graph / X)
src/pages/og/[card].png.ts(logic insrc/lib/og.ts). A card holds the product name, the page's headline (its h1, which matches the meta title) and the platforms:node_modules(@fontsource/geist,@fontsource/geist-mono,fontkit). The images no longer depend on the fonts installed on the build machine. The liveog-image.pngwas being redrawn by CI in DejaVu Sans. A build on Linux (Node 22, as in CI) gives byte-identical PNGs to a build on macOS.og:image,og:image:typeandog:image:alt, with the alt in the page's language. The alt leaves out labels the headline already names, so the Lite alt does not list the platforms twice. Docs pages areog:typearticlewitharticle:published_timeandarticle:modified_time, and the docs home stayswebsite.twitter:cardissummary_large_image. There is notwitter:sitebecause ThinkWatch has no X account.scripts/generate-og.mjs,scripts/og-image.svgandpublic/og-image.pngare removed. Nothing links to/og-image.png.Indexing
noindexand has no canonical,hreflangorog:url. Its language choice now leads to the home page instead of the missing/404/.<lastmod>used to be the build time for every URL. It is now the date of the last commit that touched the files a page is built from (src/lib/lastmod.ts), or, when later, the date of the latest release of a product the page shows in its content or structured data: Lite for /lite, Core for /core, Enterprise for /thinkwatch, and all three for the home page (the Lite download button and the structured data of every product) and for the changelog. The home page's sources include the hero, the terminal, the console mockups and the Lite download button. Without git history (a shallow clone),<lastmod>is omitted.deploy.ymlchecks out the full history (fetch-depth: 0). It also passesGITHUB_TOKENto the build, which already read it but was never given it, so every build ran against the anonymous API limit. The build job runs npm packages with that token in its environment, so it getscontents: readonly;pages: writeandid-token: writebelong to the deploy job alone.Changelog and RSS
/changeloglists every release of Enterprise, Lite and Core from the GitHub API (product, version, date, link to the release), newest first, with a product filter (All · Enterprise · Lite · Core) in place of the type filter. The Enterprise notes written for this site (v0.1.0–v0.4.0) keep their content, and the zh page still prefers the Chinese notes. Summaries are not invented.src/data/releases.json. Refresh it withpnpm releases. Tested with a bad token: all 85 entries still render and versions are left out of the JSON-LD.https://thinkwat.ch/changelog#v0.4.0), so feed readers do not show them again. Every Enterprise entry on the page keeps its old anchor (#v0.4.0) next to the new one (#enterprise-v0.4.0), so old links still lead to the release. The channel links to /changelog/ and declares its language.ChangelogPage.astro, with copy insrc/i18n/pages/changelog.ts, following the other pages.Alt text
The Lite page's feature screenshots used
alt={item.title}. Each item now has analtinsrc/i18n/pages/lite.ts(en and zh-CN) that describes what the screenshot shows. The images are unchanged.Also
Checkworkflow builds the site for every pull request.deploy.ymlonly runs on main, so a broken build used to show up only as a failed deploy. Drop commit 6b963b6 (ci: build the site for every pull request) if this is not wanted.ChangeFreqEnum, which fixes the seven type errors inastro.config.mjs.Base.astrointosrc/i18n/pages/home.ts, unchanged.Verification
pnpm build(pnpm 10) passes: 45 pages, 4 cards, sitemap, feed.tsc --noEmitpasses.astro checkreports only the existingFeaturesSection.astroerror.@typeand property exists in the current schema.org vocabulary with a matching domain.dist/(4,462), including the feed's links and the sitemap. All 111 external links return 2xx or 3xx.node:22Linux container from a fresh full clone; the Check workflow builds every push on Linux with Node 22.src/data/releases.jsonand GitHub unreachable, a Core release moves<lastmod>of /, /core and /changelog only, and Lite and Enterprise releases move /, /lite, /thinkwatch and /changelog. In Chrome,/zh-CN/changelog/#v0.1.0lands on the release exactly where#enterprise-v0.1.0does. A build without git history emits no<lastmod>and no article dates.actionlintpasses on both workflows.🤖 Generated with Claude Code