SEO and the report
SEO is the most oversold part of any directory starter, so first: what is promised, and what is not.
Promised. A title and description on every page from a template you control, cut at word
boundaries. A self-referencing canonical, including on /page/2/. A sitemap split by page kind. A
robots.txt that disallows every dynamic route. JSON-LD chosen from site.kind, escaped so a
submitted name cannot break out of the script tag. Thin category and tag pages go noindex and
leave the sitemap on their own.
Not promised. No rich results for ratings: with no review data, aggregateRating and review
are never emitted, and a test enforces it. No FAQ rich results: the FAQ on generated pages is
visible copy with no FAQPage schema. No sitelinks search box.
Titles and descriptions
Section titled “Titles and descriptions”seo.templates holds a titleTemplate wrapper and a title and description pair for home,
listing, category, tag, pseo and blog. Override a pair; the rest keep their defaults.
seo: { templates: { titleTemplate: '{title} | {siteName}', category: { title: 'Best {category} {nouns} ({count})', description: 'Compare {count} {category} {nouns}, hand-picked and updated for {year}.', }, },},The placeholders are {title}, {siteName}, {name}, {tagline}, {category}, {tag},
{count}, {noun}, {nouns} and {year}; the last two come from routes.listingNoun. Anything
else fails pnpm check:config with unknown placeholder {x}.
A title is the titleTemplate result when that is 60 characters or fewer, else the bare page title
when that is 65 or fewer, else the page title cut at a word boundary to 65. A description is cut at
a word boundary to 160 characters. Page 2 onwards of a category or tag gets a - Page n suffix.
Canonicals and trailing slashes
Section titled “Canonicals and trailing slashes”Every canonical is site.url plus the page path, and every page path ends in a slash. A paginated
page canonicalises to itself — /categories/writing/page/2/ points at page 2, never at page 1 —
and there is no rel="prev" or rel="next". site.url is baked in at build time, with the
sitemap, the feeds and the OG image URLs: set it to your real domain before the first build you
deploy.
Sitemap and robots
Section titled “Sitemap and robots”The build writes sitemap-index.xml and one chunk per page kind.
| Chunk | Contains | Frequency, priority |
|---|---|---|
sitemap-listings-0.xml |
every listing; lastmod is the approval date |
weekly, 0.8 |
sitemap-taxonomy-0.xml |
category and tag pages, their indexes and pagination | weekly, 0.7 |
sitemap-pseo-0.xml |
the generated pages, from the exact slug list | weekly, 0.6 |
sitemap-blog-0.xml |
/blog/ and every post; lastmod is the updated or publish date |
monthly, 0.5 |
sitemap-pages-0.xml |
everything else; / is daily, 1.0 |
plugin defaults |
Dropped: /search/, /404/, every noindex page, anything under a robots.txt Disallow:
prefix, and file endpoints such as /rss.xml.
/robots.txt is User-agent: *, one Disallow: per prefix, then
Sitemap: <site.url>/sitemap-index.xml. The prefixes are a fixed list — /admin/, /api/,
/_actions/, /submit/, /search/, /login/, /account/, /auth/ — plus seo.robots.disallow
(by default /out/ and /submit/status/ on top), plus /out/ whenever features.clicks is
'redirect'.
noindex,follow goes on /search/, /submit/ and /admin/ always, and on a category or tag page
with fewer listings than seo.noindexBelow (default 2): an empty category is still built, but
noindexed and out of the sitemap.
Structured data
Section titled “Structured data”seo.jsonLd.listing picks the listing type: software (Product plus SoftwareApplication),
product, organization, localBusiness (Organization unless the listing’s extra has
address and telephone), or none. Unset, it follows site.kind (software → software;
service → organization; business → localBusiness). The home page emits WebSite and, under
jsonLd.organization, Organization. jsonLd.breadcrumbs adds a BreadcrumbList to listing,
category and generated pages; jsonLd.itemList adds an ItemList of at most 24 entries to
category, tag and generated pages. Generated pages emit no FAQPage.
Internal links
Section titled “Internal links”The related block on a listing page takes every listing sharing a tag or the category, scores each
as 0.6 × tag overlap (Jaccard) + 0.3 × normalised popularity + 0.1 when featured, and keeps the
top listing.relatedCount (default 6) by score, then popularity, then slug.
features.relatedLinks: false renders nothing there. “Appears in” lists the generated pages that
include the listing, largest first, at most 8.
An orphan is a listing with fewer than 2 other pages linking to it. Under
seo.strictLinks: true it fails the build; under false, the default, the build warns
N orphan listing(s); set seo.strictLinks: true to fail the build on this.
The build report
Section titled “The build report”The report scans every index.html under dist/client and prints four lines:
seo-report: scanned N pages; L listings; X noindex; pseo P pages (best-category-for-tag=A, name-alternatives=B)seo-report: categories without pSEO coverage: noneseo-report: sitemap: N urls expected (blog=…, listings=…, pages=…, pseo=…, taxonomy=…) — dist sitemap matchesseo-report: inbound links per listing: min M; orphans (fewer than 2 inbound): 0 [strictLinks=on]Warnings, never failures: a route with no index.html, an indexable page missing from
sitemap-*.xml, a sitemap entry the rules should have dropped, and a listing or generated page with
more than 30 internal links inside <main>. The one failure is an orphan under
seo.strictLinks: true:
seo-report: 1 orphan listing(s) with fewer than 2 inbound links (seo.strictLinks=true): /tools/my-tool/ (1 from /categories/writing/page/2/)Neighbouring lines from other steps: pseo: generated N (dropped by minItems M, truncated K)
is the generator’s count, see Programmatic SEO. budget: N files / 20000 (P%) — free plan, ok counts every
file in dist/client against the plan limit, warns above 80% and fails above 90%, see
Cost and limits. zero-js: N content pages, 0 islands, 0 external scripts — ok
fails when a content page other than /search/ ships a script.
Lighthouse
Section titled “Lighthouse”pnpm lhci is not part of the build. Run it after pnpm build: it reads the route list the build
wrote, audits the first indexable page of each kind five times on the desktop preset, and fails
when the median of any category — performance, accessibility, best practices, SEO — is below 0.95.
noindex pages are skipped. Reports stay in .lighthouseci/.
Verify
Section titled “Verify”| Where | You should see | |
|---|---|---|
| Title, canonical, JSON-LD | view-source:https://<your domain>/<listingBase>/<slug>/ |
one <title>; a <link rel="canonical"> on your domain ending in a slash; JSON-LD naming the listing |
| Robots | https://<your domain>/robots.txt |
User-agent: *, the Disallow: lines, Sitemap: https://<your domain>/sitemap-index.xml |
| Sitemap | https://<your domain>/sitemap-index.xml |
one <loc> per chunk; sitemap-listings-0.xml lists your listings |
| The report | the build output | the four seo-report: lines, ending orphans (fewer than 2 inbound): 0 |
Configuration
Section titled “Configuration”| Field | Default | What it changes |
|---|---|---|
seo.templates.titleTemplate, seo.templates.<kind> |
'{title} | {siteName}'; the pairs above |
the title and description of each page kind |
seo.jsonLd.listing, .breadcrumbs, .itemList, .organization |
from site.kind; true; true; true |
which JSON-LD each page emits |
seo.noindexBelow |
2 |
the listing count below which a category or tag page is noindex |
seo.robots.disallow |
['/admin/', '/api/', '/out/', '/submit/status/', '/search/'] |
extra Disallow: prefixes, also dropped from the sitemap |
seo.strictLinks |
false |
whether an orphan fails the build |
features.relatedLinks |
true |
the related block on listing pages |
listing.relatedCount |
6 |
how many related listings, 0 to 24 |
See seo, features and
listing.
Directorysrc/
Directorylib/
- seo.ts titles, canonical,
noindex, robots prefixes, sitemap chunks - jsonld.ts the JSON-LD builders
- paths.ts every URL, always with a trailing slash
- seo.ts titles, canonical,
- components/seo/Head.astro the head tags
- pages/robots.txt.ts
/robots.txt - features/paid/related.ts the related-links engine
Directoryintegrations/
- sitemap.ts the chunked sitemap and its drop rules
- seo-report.ts the report and the orphan failure
- lighthouserc.cjs
pnpm lhci