Repository navigation
SEO: launch fixes across metadata, brand rasters and the hero preload - #38
Merged
Merged
Conversation
The Worker's own workers.dev subdomain serves the whole site and stays enabled by choice (#34), and the preview aliases from non-production branch builds live on the same domain. Both are public, and robots.txt says Allow: /. The prerendered canonical already points at execil.net, which Google usually honours; X-Robots-Tag is the signal that does not depend on it being honoured. _headers matches host-first when the rule is an absolute URL, and `:name` is a placeholder for one label, so a single rule covers execil.<subdomain>.workers.dev and every <alias>-execil.<subdomain> preview. No Worker change and no run_worker_first: these are asset responses, which is what every prerendered page is. nosniff and Referrer-Policy come along while the file is open. Not HSTS — that one binds the apex and every subdomain for as long as browsers remember it, so it is a decision, not a commit.
logo.png is new. Organization.logo pointed at og.png, which is a 1200x630 social card with a headline across it — Google reads that field for the knowledge panel and wants the logo itself, on white, 112px minimum in both axes. The new file is the wordmark on white at 1024x406. The OG card's lockup was still the placeholder mark, a teal square with a green square inside it. Both now draw the real wordmark from the same geometry as public/brand/cross-dot/. Two things about drawing it in Pillow. Glyph pen positions are listed rather than laid out, because this Pillow has no Raqm: its own layout drops the kerning, which would set the word 25 units wide and land the badge 78 units late. And the knockout ring samples the canvas before a letter is drawn — after the text, that point is the tittle, and the ring came out black.
The meta description was 215 characters against Google's ~155, so it was being cut mid-sentence in results. Now 155. The 404 inherited the home page's title and its `index, follow`. Its 404 status is what actually keeps it out of the index, but the tab said "Execil | Medical Billing & RCM" and a crawler reading markup before status saw an invitation. It cannot set a route head — nothing matched, so there is no route to hang one on. React 19 hoists a rendered <title> into <head>, but the root head's title is emitted first and wins, which left the document with two. So the title moves to the home route, and the root keeps none. The robots meta is deleted outright rather than moved: `index, follow` is the default, so it only existed to collide. Sitemap gains lastmod, the one field there Google reads.
The picture is the LCP element on narrow screens, where it sits above the copy. Markup alone is not enough there: the preload scanner finds the <img> only once the CSS that sizes it has arrived, and fetchpriority cannot start a request nobody has discovered yet. Verified on the built site at 375px — one request for the hero, initiator "link", the 900w candidate, and no "preloaded but not used". That needs the same src, srcset and sizes in two files, so they move to hero-image.ts. A mismatch there is not a no-op: the browser resolves a different candidate and downloads the picture twice. FAQPage covers the eight questions. Expectations first: since 2023 Google shows FAQ rich results only for government and health *authority* sites, and Execil is neither. It is valid, cheap, and read by other consumers of structured data. It sits on the home route rather than beside organizationLd in the root, because the questions render on one page.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
execil | 446f15c | Commit Preview URL Branch Preview URL |
Sep 08 2026, 12:56 AM |
This was referenced Sep 8, 2026
Abuubkar
added a commit
that referenced
this pull request
Sep 13, 2026
…ts.txt (#44) The code half of pre-launch SEO (#38) covered the search crawlers. This covers the answer engines, which read different things. Structured data was two disconnected islands: Organization from the root, a FAQPage from the home route, neither carrying an @id, so nothing referred to anything. Now every page emits Organization and WebSite with stable @ids, and each route adds its own nodes referencing them — the home page a WebPage, the Service with the Services section as a nested OfferCatalog, and the FAQ; the legal pages a WebPage with dateModified and a BreadcrumbList. Consumers merge a graph by @id across script tags, so the publisher is described once. Every value is derived from messages.json, including Organization.knowsAbout, which is the specialty list — the thing an engine asked "who bills for dermatology practices" has to match on. public/llms.txt restates the page as plain text an answer engine can quote: services, specialties, the comparison table, the FAQ. Generated by scripts/llms-txt.mjs and run from `build`, because a second hand-kept copy of these facts is the same drift the brand assets already taught us about. The output is committed so a copy change shows up in the diff. robots.txt names the AI crawlers. Cloudflare's managed robots.txt is prepended at the edge and disallows GPTBot, ClaudeBot, Google-Extended and five others by default; the real fix is the dashboard toggle, but repeating a user-agent merges the groups and an equal-specificity Allow wins the tie-break. FAQ answers get an id each, so one answer can be cited on its own.
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.
The code half of the pre-launch SEO list. Baseline was already good — Lighthouse
SEO 100, accessibility 100, and
0 placeholders remaining— so this is a set ofspecific gaps rather than a rescue. Four commits, each standing alone.
Non-canonical hostnames —
_headersexecil.<sub>.workers.devserves the whole site and stays enabled by choice(#34); the preview aliases from non-production branch builds live on the same
domain. Both are public, and
robots.txtisAllow: /.One
_headersrule covers all of them:Absolute-URL rules match host-first and
:nameis a one-label placeholder, sothis catches
execil.<sub>.workers.devand every<alias>-execil.<sub>previewalike. No Worker change and no
run_worker_first— these are asset responses,which every prerendered page is. Wrangler confirms:
Parsed 4 valid header rules.nosniffandReferrer-Policycome along too. Deliberately not HSTS — thatbinds the apex and every subdomain for as long as browsers remember it, so it
wants your decision, not a commit.
Organization.logoand the OG cardlogo.pngis new.Organization.logopointed atog.png, a 1200×630 socialcard with a headline across it; Google reads that field for the knowledge panel
and wants the logo itself, on white, 112px minimum in both axes. Now the
wordmark on white at 1024×406.
The OG card's own lockup was still the placeholder teal-square mark. Both now
draw the real wordmark from the same geometry as
public/brand/cross-dot/.Two notes for whoever next runs
scripts/og-image.py, both recorded in thefile: glyph pen positions are listed rather than laid out, because this Pillow
has no Raqm and its own layout drops the kerning — it would set the word 25
units wide and land the badge 78 units late. And the knockout ring samples the
canvas before any letter is drawn; sampling after put it on the tittle and the
ring came out black.
Description, titles, 404, sitemap
The meta description was 215 characters against Google's ~155, so results cut it
mid-sentence. Now exactly 155, with the same claims.
The 404 inherited the home page's title and its
index, follow. This oneneeded a restructure, and it is the part worth reviewing. A not-found page
cannot set a route head — nothing matched, so there is no route to hang one on.
React 19 hoists a rendered
<title>into<head>, but the root head's title isemitted first and wins, which left the document carrying two titles and two
contradictory robots metas. My first attempt shipped exactly that; the build
output is what caught it.
So the title moved to the home route and the root keeps none — every route
already set its own except home. The robots meta is deleted rather than moved:
index, followis the crawler default, so it existed only to collide.Verified on the built site:
/Execil | Medical Billing & RCM…/privacyPrivacy Policy | Execil/does-not-existThat page does not exist | Execilnoindex, followOne title per page, and the 404 status is unchanged — that is what actually
keeps it out of the index; the meta is for the tab and for a crawler reading
markup before status.
Sitemap gains
lastmod, the one field there Google reads, dated from when eachpage's source last changed.
Hero preload and FAQPage
The picture is the LCP element on narrow screens, where it sits above the copy.
Markup alone is not enough: the preload scanner finds the
<img>only once theCSS that sizes it has arrived, and
fetchprioritycannot start a request nobodyhas discovered. Verified on the built site at 375px — one request for the
hero,
initiatorType: "link", the 900w candidate, and no "preloaded but notused" warning.
That needs identical
src,srcsetandsizesin two files, so they moved tosrc/hero-image.ts. A mismatch there is not a no-op: the browser resolves adifferent candidate and fetches the picture twice.
FAQPage covers the eight questions, on the home route rather than beside
organizationLdin the root, because they render on one page. Expectationsfirst: since 2023 Google shows FAQ rich results only for government and health
authority sites, and Execil is neither. It is valid, cheap, and read by other
consumers of structured data — the accordion is not the reason to have it.
Checked
pnpm checkandpnpm buildpass. Lighthouse against the production build:SEO 100, best-practices 100. Both JSON-LD blocks parse;
Organization.logoresolves to
/logo.png.Best-practices reads 81 on the live site, not because of anything here — three
deprecation warnings, all from Cloudflare's Bot Fight Mode script at
/cdn-cgi/challenge-platform/…. Turning that off is a dashboard change.Still yours, not code
Sitemap submission to Search Console (the open item on #34), a
www→ apexredirect rule, and the Google Business Profile decision.