feat(seo): add meta description, canonical link and sitemap - #3
Merged
Conversation
The landing page shipped without a meta description or canonical URL, and the site exposed no sitemap. Indexing directives move into their own `seo` block in base.html so pages can opt out of the index without restating every og:/twitter: tag, which `block meta` would otherwise force. Paste URLs are capability URLs β the ID grants access to the ciphertext and the key lives in the fragment. Indexing one distributes that capability, so paste, raw and error pages are noindex with no canonical, and the sitemap lists only the landing page rather than walking storage. Link-preview bots read pages directly, so unfurls are unaffected. No Disallow is added for paste paths: a blocked path stops crawlers from ever reading the noindex directive.
Contributor
Author
|
Merging with That job is failing on untouched Lint and The pre-existing failure is tracked separately in #4, where the first diagnosis was falsified and the investigation is still open. It is unrelated to this change, which touches no production async code. |
This was referenced Jul 20, 2026
Merged
ClaraVnk
added a commit
that referenced
this pull request
Jul 20, 2026
Nothing has shipped since v1.5.0: production still serves that build, so the SEO, privacy, performance and accessibility work merged in #3 and #7-#10 is present on main and absent from ghostbit.dev. Pushes to main only publish the `edge` and `sha-*` image tags. The semver and `latest` tags β which is what a pinned deployment follows β are produced only by a v*.*.* git tag, and the last one predates all of it. Server changes in this release: - meta description, canonical link, /sitemap.xml, noindex on paste pages - star count fetched server-side; no third-party call from the browser and connect-src back to 'self' - landing page 928 KB -> 336 KB, LCP 6.0s -> 3.0s; paste page 515 KB -> 307 KB - form controls labelled, <main> landmark, light-mode contrast fixed - dead static assets removed, including a source map misnamed .sha256 Note the CLI has no changes since v1.5.0, yet this publishes ghostbit-cli 1.6.0 to PyPI with identical code: release.yml reads its version from cli/pyproject.toml, so a server-only release cannot be cut without it. Worth decoupling.
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.
Contexte
The landing page shipped without a
<meta name="description">or a canonical URL, and the site exposed no sitemap β the three gaps an SEO audit flags first.Ghostbit is a zero-knowledge paste service, so "add SEO metadata" is not uniformly desirable: a paste URL is a capability. The ID grants access to the ciphertext and the decryption key lives in the fragment. Getting one into a search index distributes that capability to everyone. The indexing surface is therefore deliberately limited to the landing page.
Changements
meta descriptionand<link rel="canonical">on the landing page, built through the existingabs_url()helper so they respectBASE_URLbehind a TLS-terminating proxy.{% block seo %}inbase.html, separate fromblock meta. Needed becausepaste.htmloverridesblock metawholesale β without the split, every template opting out of indexing would have to restate allog:/twitter:tags.noindex, nofollowonpaste.html,raw.htmlanderror.html, with no canonical on those pages (a noindex page advertising itself as canonical is contradictory).GET /sitemap.xmllisting a single entry (/) β it does not walk storage.Sitemap:directive inrobots.txt, emitted per request as an absolute URL so self-hosters on any domain get a correct one with no configuration.No
Disallowwas added for paste paths, deliberately: a path blocked inrobots.txtprevents crawlers from ever reading thenoindexdirective on it, which is the opposite of the intent.Tests
tests/test_api.py: sitemap directive inrobots.txt, single-entry sitemap with correct content type, paste page carriesnoindexand no canonical, landing page carries description + canonical and nonoindex.ruff checkandruff format --checkclean onapp/ tests/ cli/./robots.txtand/sitemap.xmlinspected by hand.Not exercised in a browser: the change is
<head>tags plus two text endpoints, with no visual surface.Risques
Low. No behavior change for existing routes; the only new route is
/sitemap.xml, registered ahead of the/{paste_id}catch-all (and unreachable by it anyway β the ID pattern rejects dots).The one judgement call worth review is the
noindexon paste pages. It is a privacy tightening rather than a pure SEO fix, and it does not affect link-preview unfurls: Slack/iMessage bots fetch pages directly instead of going through a search index.