fix(website): unescape JSON-LD strings, answer "is this Screenly OSE?" - #3222
Merged
vpetersson merged 1 commit intoJul 28, 2026
Merged
Conversation
Two things, both found while looking at why the old product name
converts so badly in Search Console.
1. Every JSON-LD string value is wrapped in literal quote characters.
Go's html/template applies contextual auto-escaping inside <script>,
so `{{ .Title | jsonify }}` gets escaped a second time as a JS
string. The emitted value is "\"Installation Options\"" rather than
"Installation Options". The JSON stays valid, which is why this went
unnoticed, but every headline, description and FAQ question carries
stray quotes into Google's rich results.
19 affected values: TechArticle headline + description on all 9 docs
pages, the site description, and all 29 FAQ questions and answers.
Fixed by adding `| safeJS` after `jsonify` at all five call sites —
jsonify already produces correctly escaped JSON, so the second pass
was pure corruption. All 11 ld+json blocks now parse clean with zero
stray-quote values.
2. The site never mentioned "Screenly OSE" — its own former name.
Search Console, last 90 days: 23 old-name queries, 1,962 impressions,
43 clicks (2.19% CTR). "screenly ose" alone is 1,253 impressions at
position 6.8 for 17 clicks; "screenly ose pi3" and "screenly ose
pi 3" are 429 impressions for zero clicks.
Where the two names appear together, the same searchers convert an
order of magnitude better: "anthias (screenly ose)" is 16 clicks from
31 impressions (51.6%) and "anthias screenly ose" 9 from 20 (45%).
People looking for the old name do want this project — the site just
never confirms they are in the right place.
Adds an FAQ entry (inherits the existing FAQPage schema, so it is
eligible as a rich result) citing the December 2022 announcement
linked from the repo README, plus one line under the homepage hero.
hugo 0.157.0 (the version pinned in deploy-website.yaml) builds clean.
|
vpetersson
approved these changes
Jul 28, 2026
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.



Two changes, both found while investigating why the old product name converts so badly in Search Console.
1. Every JSON-LD string value carries literal quote characters
Go's
html/templateapplies contextual auto-escaping inside<script>, so{{ .Title | jsonify }}is escaped a second time as a JS string. What ships is:instead of:
The JSON stays valid, which is why this went unnoticed — but the string value has stray quotes, so Google renders
"Installation Options"(with quotes) in rich results.19 affected values:
/docs/*pagesTechArticleheadline+descriptiondescriptiononSoftwareApplication/faq/Questionnames andAnswertextFixed by appending
| safeJSat all fivejsonify-inside-<script>call sites inbaseof.html.jsonifyalready emits correctly escaped JSON — the second escaping pass was pure corruption, andsafeJSjust stops it. Values come fromdata/faq.yamland page frontmatter, not user input.Verified: all 11
ld+jsonblocks across the built site parse as valid JSON with zero stray-quote values (was 19).2. The site never mentioned "Screenly OSE" — its own former name
Search Console, last 90 days: 23 old-name queries, 1,962 impressions, 43 clicks — 2.19% CTR.
Now compare the queries where both names appear:
Same searchers, ~30× the CTR. People looking for the old name genuinely want this project — the site just never confirms they've landed in the right place. Every
grepfor "Screenly OSE" acrosscontent/,layouts/anddata/came back empty.Adds:
FAQPageschema, so it's eligible as a rich result for exactly these queries. Cites the December 2022 announcement already linked from the repo README.Checks
bun run buildwith hugo 0.157.0 (the version pinned indeploy-website.yaml) — clean, 18 pageswebsite/assets/styles/style.cssdeliberately not included. My local Tailwind resolve produced 59 lines of churn unrelated to this change, andcss:buildregenerates it during deploy anyway.Not changed
Worth recording, since it looks alarming in Search Console but isn't a defect:
/docs/,/features/and/api/show large zero-click impression counts. Those impressions come from brand queries (anthias,anthias digital signage) where the homepage ranks Manual for downloading the git repository doesn't work like expected #1 and these pages appear as sitelinks on the same SERP — users click the homepage./api/earns all 25 of its clicks fromanthias apiat position 1.5, the one query matching its intent. Working as intended.digital signage raspberry pihas 4,509 impressions at position 4.0 with 0.2% CTR, whileraspberry pi digital signagehas 1,009 at position 4.5 with 5.0%. Same page, same position, 25× CTR gap — that difference lives in the SERP layout, not in this repo.Opened from a fork:
vpetersson-bothas read-only access here.🤖 Generated with Claude Code
https://claude.ai/code/session_017Na8LvnqCPqMHRC5LwEbhB