Google was reading “Loading…” , and other SEO adventures
The audit caught something embarrassing. Curl the production URL of any detail page and the body was just the string "Loading...", meta tags and JSON-LD were
The audit caught something embarrassing. Curl the production URL of any detail page and the body was just the string "Loading...", meta tags and JSON-LD were fine, but the actual h1, description, cast, tags, every bit of renderable content was invisible to crawlers. Zero of 708 discovered pages were indexed in Search Console. The root cause: the server component was correctly fetching the video / actor / studio data to build generateMetadata, then throwing the result away and rendering <PageClient /> with no data; PageClient refetched the same payload in a client-side useEffect, and until the fetch resolved, short-circuited with <div>Loading...</div>. Under SSR that short-circuit is the HTML Googlebot sees. Fixed by threading the server-fetched data through as an initial* prop, initialising useState from it, and dropping the loading guard entirely. Nine page types rewritten; curl now returns zero matches for "Loading" across all of them.
With crawlers finally seeing real content, the day became a structured-data marathon. Site-wide WebSite + Organization schema in the root layout (with a SearchAction pointing at /search?q={search_term_string}). BreadcrumbList on every detail page. The sitemap generator gained real lastmod timestamps (Google ignores changefreq and priority, so those got dropped). VideoObject schema enriched with publisher + ISO-8601 uploadDate + embedUrl where available. CollectionPage + ItemList on every listing. Actor Person.description rebuilt from structured facts (country, earliest release year, top studios, video count) because the old FreeOnes bio was the same near-duplicate paragraph on roughly 1,915 pages. Listing meta descriptions expanded from 56-74 chars to proper 143-145 char anchor sentences. And one embarrassing grammatical fix: "1 Videos, 1 Studios" became "1 Video, 1 Studio" after count-aware pluralisation.
Other wins: rel="nofollow" added to every affiliate link, width / height attributes on every card image so browsers reserve layout space before decode (fixes Cumulative Layout Shift), noindex on /search and /feed so infinite thin query-string variants don't dilute the index. Open Graph coverage went from zero on home + every listing to full summary_large_image everywhere, with Organization logo + a default 1200×630 OG image generated by a new sharp-based dev utility. Cloudflare's "Block AI Scrapers" setting was silently 403'ing ClaudeBot, PerplexityBot, GPTBot, toggled off in the dashboard, and llms.txt went up as the structured entry point for AI crawlers that can now reach the site. And a site-wide orientation filter landed (straight / gay / trans pills in the header, persists to localStorage, triggers a reload, every list endpoint honours ?orientation=X) so the catalogue can responsibly expand to off-default orientations without mixing them into the default view.