From f7147bdb446605da3a482ce2927395911c7c0611 Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Sun, 1 Nov 2026 01:39:01 -0700 Subject: [PATCH] fix(web): SEO fixes from Search Console and Lighthouse audit (#1039) * fix(web): SEO fixes from Search Console and Lighthouse audit Technical: - Emit BreadcrumbList JSON-LD from the same trail as the visual breadcrumbs on every SeoSubpageShell page. - absoluteUrl() no longer appends a trailing slash to file paths. The Organization and publisher logo URLs pointed at /vocalinux.png/, a 404. - Fetch the GitHub star count once at build time in next.config.js and drop the client-side api.github.com fetch and preconnect (Lighthouse "third-party code" on every page). Falls back to the "GitHub" label. - Fix h1 -> h3 heading skips on 11 pages by promoting the leading card headings to h2; new .subpage-card-title keeps the old h3 sizing. - Header and footer logo get alt="" next to the visible "Vocalinux" text. - Footer links every guide, distro and feature page, so the pages added in #1038 and /vs-nerd-dictation/ (Discovered, not indexed) and /writers/ (no inbound links) are linked sitewide. Titles and descriptions: - Rewrite titles/descriptions for pages with high impressions and low CTR in Search Console (home, FAQ, compare, languages, open-source, GPU, alternatives, offline, install) to lead with the queries they rank for. - Distro pages use " Speech to Text: Offline Voice Typing Setup" as the search title; the on-page h1 is unchanged. - Remove brand repetition and shorten 75+ character titles. - Bump sitemap lastmod for the rewritten pages. * fix(web): keep home install card titles styled The home page's install cards still use h3; only /install/ moved to h2. Style both so the home cards keep their size and spacing. --- .github/workflows/unified-pipeline.yml | 3 ++ web/next.config.js | 30 +++++++++++- web/public/sitemap.xml | 24 +++++----- web/src/app/alternatives/page.tsx | 4 +- web/src/app/autostart/page.tsx | 2 +- web/src/app/changelog/page.tsx | 2 +- web/src/app/compare/page.tsx | 4 +- web/src/app/custom-dictionary/page.tsx | 6 +-- web/src/app/dictation-pad/page.tsx | 6 +-- web/src/app/faq/page.tsx | 4 +- web/src/app/for-developers/page.tsx | 2 +- web/src/app/gpu-acceleration/page.tsx | 4 +- web/src/app/install/[distro]/page.tsx | 4 +- web/src/app/install/page.tsx | 14 +++--- web/src/app/languages/page.tsx | 6 +-- web/src/app/layout.tsx | 9 ++-- web/src/app/multilingual-dictation/page.tsx | 6 +-- web/src/app/offline/page.tsx | 6 +-- web/src/app/open-source/page.tsx | 4 +- web/src/app/privacy/page.tsx | 6 +-- web/src/app/screenshots/page.tsx | 2 +- web/src/app/shortcuts/page.tsx | 2 +- web/src/app/transcribe-audio-files/page.tsx | 8 ++-- web/src/app/troubleshooting/page.tsx | 2 +- web/src/app/use-cases/page.tsx | 2 +- web/src/app/vs-nerd-dictation/page.tsx | 2 +- web/src/app/wayland/page.tsx | 2 +- web/src/app/whisper-model-guide/page.tsx | 2 +- web/src/app/writers/page.tsx | 2 +- .../__tests__/optimized-image.test.tsx | 5 ++ web/src/components/optimized-image.tsx | 12 ++++- web/src/components/seo-subpage-shell.tsx | 47 ++++++++++++++----- web/src/components/site-chrome.tsx | 34 ++++++++------ web/src/lib/__tests__/seo.test.ts | 23 +++++++++ web/src/lib/seo.ts | 7 +++ web/src/lib/site-nav.ts | 21 +++++++-- web/src/styles/globals.css | 5 +- 37 files changed, 221 insertions(+), 103 deletions(-) create mode 100644 web/src/lib/__tests__/seo.test.ts diff --git a/.github/workflows/unified-pipeline.yml b/.github/workflows/unified-pipeline.yml index 957e16b..c11d15b 100644 --- a/.github/workflows/unified-pipeline.yml +++ b/.github/workflows/unified-pipeline.yml @@ -576,6 +576,9 @@ jobs: - name: Install and build working-directory: ./web + env: + # next.config.js reads the repo star count at build time + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | npm ci || npm install npm run deploy diff --git a/web/next.config.js b/web/next.config.js index 992156e..063779c 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -1,3 +1,5 @@ +import { PHASE_PRODUCTION_BUILD } from 'next/constants.js'; + // @ts-ignore /** @type {import("next").NextConfig} */ const config = { @@ -70,4 +72,30 @@ const config = { return config; }, }; -export default config; + +// The header star count is fetched once per production build and inlined, so +// visitors never call api.github.com (Lighthouse flagged it as third-party +// code on every page). Any failure falls back to the plain "GitHub" label. +async function fetchGitHubStars() { + try { + const res = await fetch('https://api.github.com/repos/VocaHQ/vocalinux', { + headers: process.env.GITHUB_TOKEN + ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } + : {}, + signal: AbortSignal.timeout(5000), + }); + if (!res.ok) return ''; + const data = await res.json(); + return data.stargazers_count ? String(data.stargazers_count) : ''; + } catch { + return ''; + } +} + +export default async function nextConfig(phase) { + if (phase !== PHASE_PRODUCTION_BUILD) return config; + return { + ...config, + env: { NEXT_PUBLIC_GITHUB_STARS: await fetchGitHubStars() }, + }; +} diff --git a/web/public/sitemap.xml b/web/public/sitemap.xml index b3252ed..a2fd7c2 100644 --- a/web/public/sitemap.xml +++ b/web/public/sitemap.xml @@ -2,37 +2,37 @@ https://vocalinux.com/ - 2026-06-11 + 2026-11-01 weekly 1.0 https://vocalinux.com/install/ - 2026-02-22 + 2026-11-01 weekly 0.9 https://vocalinux.com/install/ubuntu/ - 2026-02-22 + 2026-11-01 monthly 0.8 https://vocalinux.com/install/fedora/ - 2026-02-22 + 2026-11-01 monthly 0.8 https://vocalinux.com/install/arch/ - 2026-02-22 + 2026-11-01 monthly 0.8 https://vocalinux.com/compare/ - 2026-06-11 + 2026-11-01 monthly 0.8 @@ -50,7 +50,7 @@ https://vocalinux.com/faq/ - 2026-06-11 + 2026-11-01 monthly 0.8 @@ -80,14 +80,14 @@ https://vocalinux.com/alternatives/ - 2026-02-22 + 2026-11-01 monthly 0.8 https://vocalinux.com/languages/ - 2026-02-22 + 2026-11-01 monthly 0.8 @@ -129,13 +129,13 @@ https://vocalinux.com/gpu-acceleration/ - 2026-02-22 + 2026-11-01 monthly 0.8 https://vocalinux.com/open-source/ - 2026-02-22 + 2026-11-01 monthly 0.8 @@ -153,7 +153,7 @@ https://vocalinux.com/offline/ - 2026-02-22 + 2026-11-01 monthly 0.8 diff --git a/web/src/app/alternatives/page.tsx b/web/src/app/alternatives/page.tsx index 67d548f..6dfb510 100644 --- a/web/src/app/alternatives/page.tsx +++ b/web/src/app/alternatives/page.tsx @@ -56,9 +56,9 @@ const comparisonTable = [ ]; export const metadata: Metadata = buildPageMetadata({ - title: "Vocalinux Alternatives - Linux Voice Dictation Comparison", + title: "Linux Alternatives to Dragon, Otter.ai and Speechnotes", description: - "Compare Vocalinux to Dragon, Otter.ai, Google Voice Typing, and other speech-to-text alternatives. See why Vocalinux is the best choice for Linux voice dictation.", + "Looking for Dragon NaturallySpeaking, Otter.ai or Google Voice Typing on Linux? Compare offline Linux dictation options, including Vocalinux and Nerd Dictation.", path: "/alternatives", keywords: [ "vocalinux alternatives", diff --git a/web/src/app/autostart/page.tsx b/web/src/app/autostart/page.tsx index 64c6ddc..b594db2 100644 --- a/web/src/app/autostart/page.tsx +++ b/web/src/app/autostart/page.tsx @@ -165,7 +165,7 @@ export default function AutostartPage() { className="rounded-[12px] border border-border bg-background p-5" > -

{method.method}

+

{method.method}

{method.description}

); diff --git a/web/src/app/changelog/page.tsx b/web/src/app/changelog/page.tsx index 5684e57..7a5cc62 100644 --- a/web/src/app/changelog/page.tsx +++ b/web/src/app/changelog/page.tsx @@ -519,7 +519,7 @@ const getTypeStyles = (type: string) => { }; export const metadata: Metadata = buildPageMetadata({ - title: "Vocalinux Changelog - Release History", + title: "Changelog and Release Notes", description: "Track Vocalinux release history and version updates. See what's new in each version of our Linux voice dictation software.", path: "/changelog", diff --git a/web/src/app/compare/page.tsx b/web/src/app/compare/page.tsx index 1f3be6a..a790e3f 100644 --- a/web/src/app/compare/page.tsx +++ b/web/src/app/compare/page.tsx @@ -81,9 +81,9 @@ const engineTable = [ ]; export const metadata: Metadata = buildPageMetadata({ - title: "Whisper.cpp vs Whisper vs VOSK vs Remote API for Linux", + title: "Vosk vs Whisper vs whisper.cpp: Linux Speech Engines", description: - "Compare whisper.cpp, Whisper, VOSK, and Remote API for Linux speech-to-text. See speed, hardware requirements, privacy tradeoffs, and best use cases.", + "Vosk vs Whisper vs whisper.cpp for offline Linux speech to text, plus Faster Whisper, Parakeet and Remote API: speed, accuracy, hardware needs and privacy.", path: "/compare", keywords: [ "whisper.cpp vs vosk", diff --git a/web/src/app/custom-dictionary/page.tsx b/web/src/app/custom-dictionary/page.tsx index a649e45..b3146b5 100644 --- a/web/src/app/custom-dictionary/page.tsx +++ b/web/src/app/custom-dictionary/page.tsx @@ -164,7 +164,7 @@ export default function CustomDictionaryPage() {