feat(web): overhaul SEO structure and intent landing pages (#210)

* feat(web): overhaul SEO structure and landing pages

Add distro-specific install pages and an engine comparison hub to capture high-intent organic queries. Centralize metadata and structured data handling to improve crawl consistency, internal linking, and rich-result eligibility.

* feat(web): add subpage navigation and visual polish

Make SEO landing pages easier to discover and navigate by adding persistent subpage header/footer links to home and key routes. Refresh install and comparison pages with colorful icon-driven sections while expanding homepage footer links for stronger internal linking.

* fix(ci): make web version sync robust to schema location

CI assumed softwareVersion lived in layout.tsx, but SEO refactors moved SoftwareApplication JSON-LD to page.tsx. Update workflow sync steps to patch whichever file currently contains softwareVersion so website build and release jobs keep working.

* chore(web): remove SEO audit artifact

Drop the internal SEO audit markdown from the PR so only production website and workflow changes are included.
This commit is contained in:
Jatin K Malik
2026-02-12 19:27:02 -08:00
committed by GitHub
parent 0d8f914993
commit bba7829201
12 changed files with 1290 additions and 238 deletions
+12 -4
View File
@@ -154,16 +154,24 @@ jobs:
run: |
VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/vocalinux/version.py)
echo "Syncing version: $VERSION"
# Update softwareVersion in layout.tsx
sed -i 's/"softwareVersion": "[^"]*"/"softwareVersion": "'"$VERSION"'"/' web/src/app/layout.tsx
# Update softwareVersion where JSON-LD currently lives
if grep -q '"softwareVersion": "' web/src/app/page.tsx; then
sed -i 's/"softwareVersion": "[^"]*"/"softwareVersion": "'"$VERSION"'"/' web/src/app/page.tsx
echo "Updated page.tsx version:"
grep -oP '"softwareVersion":\s*"\K[^"]+' web/src/app/page.tsx
elif grep -q '"softwareVersion": "' web/src/app/layout.tsx; then
sed -i 's/"softwareVersion": "[^"]*"/"softwareVersion": "'"$VERSION"'"/' web/src/app/layout.tsx
echo "Updated layout.tsx version:"
grep -oP '"softwareVersion":\s*"\K[^"]+' web/src/app/layout.tsx
else
echo "::warning::No softwareVersion field found in page.tsx or layout.tsx; skipping sync"
fi
# Update package.json version (remove -alpha/beta/rc suffix for npm)
NPM_VERSION=${VERSION%-*}
NPM_VERSION=${NPM_VERSION%-beta*}
NPM_VERSION=${NPM_VERSION%-rc*}
sed -i 's/"version": "[^"]*"/"version": "'"$NPM_VERSION"'"/' web/package.json
# Verify the changes
echo "Updated layout.tsx version:"
grep -oP '"softwareVersion":\s*"\K[^"]+' web/src/app/layout.tsx
echo "Updated package.json version:"
grep -oP '"version":\s*"\K[^"]+' web/package.json
+12 -4
View File
@@ -194,14 +194,22 @@ jobs:
run: |
VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/vocalinux/version.py)
echo "Syncing version: $VERSION"
# Update softwareVersion in layout.tsx
sed -i 's/"softwareVersion": "[^"]*"/"softwareVersion": "'"$VERSION"'"/' web/src/app/layout.tsx
# Update softwareVersion where JSON-LD currently lives
if grep -q '"softwareVersion": "' web/src/app/page.tsx; then
sed -i 's/"softwareVersion": "[^"]*"/"softwareVersion": "'"$VERSION"'"/' web/src/app/page.tsx
echo "Updated page.tsx version:"
grep -oP '"softwareVersion":\s*"\K[^"]+' web/src/app/page.tsx
elif grep -q '"softwareVersion": "' web/src/app/layout.tsx; then
sed -i 's/"softwareVersion": "[^"]*"/"softwareVersion": "'"$VERSION"'"/' web/src/app/layout.tsx
echo "Updated layout.tsx version:"
grep -oP '"softwareVersion":\s*"\K[^"]+' web/src/app/layout.tsx
else
echo "::warning::No softwareVersion field found in page.tsx or layout.tsx; skipping sync"
fi
# Update package.json version (remove -alpha suffix for npm)
NPM_VERSION=${VERSION%-*}
sed -i 's/"version": "[^"]*"/"version": "'"$NPM_VERSION"'"/' web/package.json
# Verify the changes
echo "Updated layout.tsx version:"
grep -oP '"softwareVersion":\s*"\K[^"]+' web/src/app/layout.tsx
echo "Updated package.json version:"
grep -oP '"version":\s*"\K[^"]+' web/package.json
+5
View File
@@ -3,6 +3,11 @@
User-agent: *
Allow: /
Disallow: /api/
Disallow: /_next/
Disallow: /out/
Disallow: /*?*
# Sitemap location
Sitemap: https://vocalinux.com/sitemap.xml
Host: vocalinux.com
+32 -2
View File
@@ -2,8 +2,38 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://vocalinux.com/</loc>
<lastmod>2026-02-05</lastmod>
<changefreq>monthly</changefreq>
<lastmod>2026-02-12</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://vocalinux.com/install/</loc>
<lastmod>2026-02-12</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://vocalinux.com/install/ubuntu/</loc>
<lastmod>2026-02-12</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://vocalinux.com/install/fedora/</loc>
<lastmod>2026-02-12</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://vocalinux.com/install/arch/</loc>
<lastmod>2026-02-12</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://vocalinux.com/compare/</loc>
<lastmod>2026-02-12</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
+40
View File
@@ -0,0 +1,40 @@
import fs from "fs";
import path from "path";
describe("SEO Assets", () => {
const robotsPath = path.join(process.cwd(), "public", "robots.txt");
const sitemapPath = path.join(process.cwd(), "public", "sitemap.xml");
let robotsContent: string;
let sitemapContent: string;
beforeAll(() => {
robotsContent = fs.readFileSync(robotsPath, "utf-8");
sitemapContent = fs.readFileSync(sitemapPath, "utf-8");
});
it("contains sitemap reference in robots.txt", () => {
expect(robotsContent).toContain("Sitemap: https://vocalinux.com/sitemap.xml");
});
it("blocks crawler access to technical paths", () => {
expect(robotsContent).toContain("Disallow: /api/");
expect(robotsContent).toContain("Disallow: /_next/");
expect(robotsContent).toContain("Disallow: /out/");
});
it("includes core landing pages in sitemap", () => {
const expectedUrls = [
"https://vocalinux.com/",
"https://vocalinux.com/install/",
"https://vocalinux.com/install/ubuntu/",
"https://vocalinux.com/install/fedora/",
"https://vocalinux.com/install/arch/",
"https://vocalinux.com/compare/",
];
expectedUrls.forEach((url) => {
expect(sitemapContent).toContain(`<loc>${url}</loc>`);
});
});
});
+204
View File
@@ -0,0 +1,204 @@
import Link from "next/link";
import { type Metadata } from "next";
import { CheckCircle2, Cpu, Sparkles, Zap } from "lucide-react";
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
const engineTable = [
{
engine: "whisper.cpp",
speed: "Fastest startup + low latency",
hardware: "CPU + AMD/Intel/NVIDIA GPU",
accuracy: "High (best overall balance)",
footprint: "Small models available (~39MB tiny)",
bestFor: "Most users who want strong speed + quality",
icon: Zap,
iconColor: "text-amber-500",
iconBg: "bg-amber-500/10",
},
{
engine: "Whisper (OpenAI)",
speed: "Slower install and startup",
hardware: "CPU or NVIDIA CUDA",
accuracy: "High",
footprint: "Large dependency footprint (~2.3GB)",
bestFor: "Users already standardized on PyTorch stack",
icon: Sparkles,
iconColor: "text-violet-500",
iconBg: "bg-violet-500/10",
},
{
engine: "VOSK",
speed: "Very fast realtime on low-end systems",
hardware: "CPU",
accuracy: "Good for lightweight use",
footprint: "Very lightweight (~40MB model)",
bestFor: "Older hardware and minimal-resource environments",
icon: Cpu,
iconColor: "text-cyan-500",
iconBg: "bg-cyan-500/10",
},
];
export const metadata: Metadata = buildPageMetadata({
title: "Whisper.cpp vs Whisper vs VOSK for Linux Voice Dictation",
description:
"Compare whisper.cpp, Whisper, and VOSK for Linux speech-to-text. See speed, hardware requirements, model size, and which engine is best for your workflow.",
path: "/compare",
keywords: [
"whisper.cpp vs vosk",
"linux speech recognition comparison",
"best voice dictation engine linux",
"whisper vs vosk linux",
],
});
export default function CompareEnginesPage() {
const articleJsonLd = {
"@context": "https://schema.org",
"@type": "Article",
headline: "Whisper.cpp vs Whisper vs VOSK for Linux Voice Dictation",
description:
"Technical comparison of speech recognition engines for offline Linux voice typing.",
dateModified: "2026-02-12",
author: {
"@type": "Person",
name: "Jatin K Malik",
url: "https://github.com/jatinkrmalik",
},
publisher: {
"@type": "Organization",
name: "Vocalinux",
logo: {
"@type": "ImageObject",
url: absoluteUrl("/vocalinux.png"),
},
},
mainEntityOfPage: absoluteUrl("/compare"),
};
return (
<SeoSubpageShell>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleJsonLd) }}
/>
<section>
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
<Sparkles className="h-4 w-4" />
Speech Engine Comparison
</p>
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
whisper.cpp vs Whisper vs VOSK on Linux
</h1>
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
If you are choosing a Linux speech-to-text engine, this page gives a practical side-by-side
comparison focused on latency, hardware support, install footprint, and real desktop usage.
</p>
</section>
<section className="overflow-x-auto rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-800">
<table className="w-full min-w-[860px] border-collapse text-left">
<thead>
<tr className="border-b border-zinc-200 dark:border-zinc-700">
<th className="px-4 py-3 text-sm font-semibold">Engine</th>
<th className="px-4 py-3 text-sm font-semibold">Speed</th>
<th className="px-4 py-3 text-sm font-semibold">Hardware</th>
<th className="px-4 py-3 text-sm font-semibold">Accuracy</th>
<th className="px-4 py-3 text-sm font-semibold">Footprint</th>
<th className="px-4 py-3 text-sm font-semibold">Best for</th>
</tr>
</thead>
<tbody>
{engineTable.map((row) => {
const Icon = row.icon;
return (
<tr
key={row.engine}
className="border-b border-zinc-100 align-top dark:border-zinc-700/70"
>
<td className="px-4 py-4 font-semibold">
<span className="inline-flex items-center gap-2">
<span className={`inline-flex rounded-md p-1.5 ${row.iconBg}`}>
<Icon className={`h-4 w-4 ${row.iconColor}`} />
</span>
{row.engine}
</span>
</td>
<td className="px-4 py-4 text-sm text-muted-foreground">{row.speed}</td>
<td className="px-4 py-4 text-sm text-muted-foreground">{row.hardware}</td>
<td className="px-4 py-4 text-sm text-muted-foreground">{row.accuracy}</td>
<td className="px-4 py-4 text-sm text-muted-foreground">{row.footprint}</td>
<td className="px-4 py-4 text-sm text-muted-foreground">{row.bestFor}</td>
</tr>
);
})}
</tbody>
</table>
</section>
<section className="mt-12 grid gap-6 md:grid-cols-3">
<article className="rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
<h2 className="mb-3 text-2xl font-semibold">When to pick whisper.cpp</h2>
<p className="text-sm text-muted-foreground">
Choose whisper.cpp when you want the best speed-to-accuracy ratio and broad hardware support.
It is the default in Vocalinux for a reason.
</p>
</article>
<article className="rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
<h2 className="mb-3 text-2xl font-semibold">When to pick Whisper</h2>
<p className="text-sm text-muted-foreground">
Choose OpenAI Whisper if your environment already depends on PyTorch/CUDA workflows and you
prefer that runtime profile.
</p>
</article>
<article className="rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
<h2 className="mb-3 text-2xl font-semibold">When to pick VOSK</h2>
<p className="text-sm text-muted-foreground">
Choose VOSK on older laptops, low-RAM systems, or lightweight VMs where small model size and
minimal overhead matter most.
</p>
</article>
</section>
<section className="mt-12 rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
<h2 className="mb-4 text-2xl font-bold">Next steps</h2>
<ul className="space-y-3 text-muted-foreground">
<li>
<span className="inline-flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-green-500" />
Install by distro:
<Link href="/install/ubuntu/" className="font-semibold text-primary hover:underline">
Ubuntu
</Link>
,
<Link href="/install/fedora/" className="font-semibold text-primary hover:underline">
Fedora
</Link>
,
<Link href="/install/arch/" className="font-semibold text-primary hover:underline">
Arch Linux
</Link>
.
</span>
</li>
<li>
<span className="inline-flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-green-500" />
Use interactive install to detect your hardware and pick the best engine defaults.
</span>
</li>
<li>
<span className="inline-flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-green-500" />
After install, tune model size for your preferred latency and accuracy level.
</span>
</li>
</ul>
</section>
</SeoSubpageShell>
);
}
+389
View File
@@ -0,0 +1,389 @@
import Link from "next/link";
import { type Metadata } from "next";
import { notFound } from "next/navigation";
import {
CheckCircle2,
ChevronRight,
Cpu,
Laptop,
Shield,
Sparkles,
Terminal,
} from "lucide-react";
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
import { absoluteUrl, buildPageMetadata } from "@/lib/seo";
type DistroSlug = "ubuntu" | "fedora" | "arch";
type DistroParams = { distro: string } | Promise<{ distro: string }>;
interface DistroConfig {
name: string;
title: string;
description: string;
intro: string;
prerequisites: string[];
testedOn: string[];
installCommand: string;
setupNotes: string[];
postInstallChecks: string[];
}
const distroConfig: Record<DistroSlug, DistroConfig> = {
ubuntu: {
name: "Ubuntu",
title: "Install Voice Dictation on Ubuntu",
description:
"Step-by-step Ubuntu voice dictation setup for offline speech-to-text with whisper.cpp and VOSK. Works on Ubuntu 22.04+, GNOME, KDE, X11, and Wayland.",
intro:
"This Ubuntu guide is tuned for practical desktop use: terminals, browsers, IDEs, office suites, and chat apps. Vocalinux runs locally so your voice data never leaves your machine.",
prerequisites: [
"Ubuntu 22.04+ (or Ubuntu-based distro)",
"Python 3.8+",
"Working microphone",
"curl installed (sudo apt update && sudo apt install -y curl)",
],
testedOn: [
"Ubuntu 24.04 LTS (GNOME, Wayland)",
"Ubuntu 22.04 LTS (GNOME, X11)",
"Kubuntu and Linux Mint derivatives",
],
installCommand:
"curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh --interactive",
setupNotes: [
"Choose whisper.cpp for the fastest setup and cross-vendor GPU support.",
"On lower-RAM systems, pick VOSK for lightweight realtime dictation.",
"If your desktop uses Wayland, keep keyboard injection defaults unless you know your compositor specifics.",
],
postInstallChecks: [
"Run vocalinux and confirm the tray indicator appears.",
"Double-tap Ctrl in a text field and verify speech-to-text starts.",
"Set language and model size in the settings panel for your workload.",
],
},
fedora: {
name: "Fedora",
title: "Install Voice Dictation on Fedora",
description:
"Fedora speech-to-text installation guide for Vocalinux. Set up offline Linux voice typing on Fedora Workstation with Wayland or X11.",
intro:
"Fedora is an excellent match for Vocalinux thanks to current Python stacks and up-to-date system libraries. This guide focuses on reliable setup and minimal friction.",
prerequisites: [
"Fedora Workstation 39+ (or Fedora-based distro)",
"Python 3.8+",
"Working microphone",
"curl installed (sudo dnf install -y curl)",
],
testedOn: [
"Fedora Workstation 41 (Wayland)",
"Fedora Workstation 40 (X11 fallback)",
"Fedora KDE spin",
],
installCommand:
"curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh --interactive",
setupNotes: [
"If prompted for optional dependencies, accept defaults unless your desktop setup requires custom input tooling.",
"Keep the default whisper.cpp engine for best speed-to-accuracy tradeoff.",
"Use the smallest model first, then scale up after confirming stable dictation latency.",
],
postInstallChecks: [
"Run vocalinux from terminal and test transcription in GNOME Text Editor or VS Code.",
"Verify push-to-dictate behavior with your preferred keyboard layout.",
"Confirm startup behavior and language settings persist after reboot.",
],
},
arch: {
name: "Arch Linux",
title: "Install Voice Dictation on Arch Linux",
description:
"Arch Linux voice dictation guide for offline speech recognition. Install Vocalinux on Arch, Manjaro, and EndeavourOS with distro-aware setup notes.",
intro:
"Arch users usually care about control and performance. This guide keeps setup lean while preserving compatibility across rolling-release desktop environments.",
prerequisites: [
"Arch Linux / Manjaro / EndeavourOS",
"Python 3.8+",
"Working microphone",
"curl installed (sudo pacman -Sy --needed curl)",
],
testedOn: [
"Arch Linux (GNOME, Wayland)",
"Arch Linux (KDE Plasma)",
"Manjaro (X11 and Wayland sessions)",
],
installCommand:
"curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh --interactive",
setupNotes: [
"Prefer whisper.cpp for lower dependency footprint and faster cold start.",
"On custom compositor setups, verify text injection behavior in both terminal and browser contexts.",
"If you tweak keybinds heavily, set a non-conflicting activation shortcut in Vocalinux settings.",
],
postInstallChecks: [
"Run vocalinux and test dictation in multiple apps (terminal, browser, editor).",
"Confirm your chosen speech engine model balances latency and accuracy.",
"Back up ~/.config/vocalinux/config.yaml after tuning preferences.",
],
},
};
const distroKeywords: Record<DistroSlug, string[]> = {
ubuntu: [
"ubuntu voice dictation",
"speech to text ubuntu",
"ubuntu voice typing",
"offline dictation ubuntu",
],
fedora: [
"fedora voice dictation",
"speech to text fedora",
"fedora voice typing",
"offline dictation fedora",
],
arch: [
"arch linux voice dictation",
"speech to text arch linux",
"manjaro dictation",
"offline dictation arch",
],
};
const isDistroSlug = (value: string): value is DistroSlug => {
return value in distroConfig;
};
const resolveParams = async (params: DistroParams): Promise<{ distro: string }> => {
return Promise.resolve(params);
};
const getDistroStyles = (distro: DistroSlug) => {
if (distro === "ubuntu") {
return {
icon: Laptop,
iconClass: "text-orange-500",
chipClass: "border-orange-500/30 bg-orange-500/10 text-orange-600 dark:text-orange-400",
panelClass: "from-orange-500/15 to-amber-500/10",
};
}
if (distro === "fedora") {
return {
icon: Sparkles,
iconClass: "text-blue-500",
chipClass: "border-blue-500/30 bg-blue-500/10 text-blue-600 dark:text-blue-400",
panelClass: "from-blue-500/15 to-cyan-500/10",
};
}
return {
icon: Cpu,
iconClass: "text-emerald-500",
chipClass: "border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
panelClass: "from-emerald-500/15 to-teal-500/10",
};
};
export function generateStaticParams() {
return Object.keys(distroConfig).map((distro) => ({ distro }));
}
export async function generateMetadata({
params,
}: {
params: DistroParams;
}): Promise<Metadata> {
const { distro } = await resolveParams(params);
if (!isDistroSlug(distro)) {
return buildPageMetadata({
title: "Linux Voice Dictation Install Guide",
description:
"Install Vocalinux on Linux with distro-specific setup instructions for Ubuntu, Fedora, and Arch.",
path: "/install",
});
}
const config = distroConfig[distro];
return buildPageMetadata({
title: config.title,
description: config.description,
path: `/install/${distro}`,
keywords: distroKeywords[distro],
});
}
export default async function DistroInstallPage({
params,
}: {
params: DistroParams;
}) {
const { distro } = await resolveParams(params);
if (!isDistroSlug(distro)) {
notFound();
}
const config = distroConfig[distro];
const styles = getDistroStyles(distro);
const DistroIcon = styles.icon;
const howToJsonLd = {
"@context": "https://schema.org",
"@type": "HowTo",
name: `${config.title} in 3 Steps`,
description: config.description,
inLanguage: "en-US",
totalTime: "PT10M",
supply: [
{
"@type": "HowToSupply",
name: "Linux computer",
},
{
"@type": "HowToSupply",
name: "Microphone",
},
],
tool: [
{
"@type": "HowToTool",
name: "Terminal",
},
],
step: [
{
"@type": "HowToStep",
name: "Open terminal",
text: "Launch your terminal application and ensure curl is available.",
url: absoluteUrl(`/install/${distro}`),
},
{
"@type": "HowToStep",
name: "Run installer",
text: `Run: ${config.installCommand}`,
url: absoluteUrl(`/install/${distro}`),
},
{
"@type": "HowToStep",
name: "Start dictating",
text: "Launch vocalinux and double-tap Ctrl to begin dictation.",
url: absoluteUrl(`/install/${distro}`),
},
],
};
return (
<SeoSubpageShell>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(howToJsonLd) }}
/>
<nav className="mb-6 flex items-center gap-2 text-sm text-muted-foreground">
<Link href="/" className="hover:text-foreground hover:underline">
Home
</Link>
<ChevronRight className="h-4 w-4" />
<Link href="/install/" className="hover:text-foreground hover:underline">
Install Guides
</Link>
<ChevronRight className="h-4 w-4" />
<span className="font-medium text-foreground">{config.name}</span>
</nav>
<section>
<p
className={`mb-4 inline-flex items-center gap-2 rounded-full border px-4 py-1.5 text-sm font-medium ${styles.chipClass}`}
>
<DistroIcon className={`h-4 w-4 ${styles.iconClass}`} />
{config.name} Setup Guide
</p>
<h1 className="mb-5 text-4xl font-bold tracking-tight sm:text-5xl">{config.title}</h1>
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">{config.intro}</p>
<div
className={`rounded-2xl border border-zinc-200 bg-gradient-to-br p-6 dark:border-zinc-700 ${styles.panelClass}`}
>
<h2 className="mb-3 inline-flex items-center gap-2 text-xl font-semibold">
<Terminal className="h-5 w-5 text-green-600 dark:text-green-400" />
Install command
</h2>
<div className="rounded-xl bg-zinc-950 p-4">
<code className="block overflow-x-auto whitespace-pre-wrap break-all text-sm text-green-400 sm:text-base">
{config.installCommand}
</code>
</div>
</div>
</section>
<section className="mt-10 grid gap-6 md:grid-cols-2">
<article className="rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
<h2 className="mb-4 inline-flex items-center gap-2 text-2xl font-semibold">
<Shield className="h-5 w-5 text-blue-500" />
Prerequisites
</h2>
<ul className="space-y-2 text-muted-foreground">
{config.prerequisites.map((item) => (
<li key={item} className="inline-flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
{item}
</li>
))}
</ul>
</article>
<article className="rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
<h2 className="mb-4 inline-flex items-center gap-2 text-2xl font-semibold">
<Sparkles className="h-5 w-5 text-violet-500" />
Tested environments
</h2>
<ul className="space-y-2 text-muted-foreground">
{config.testedOn.map((item) => (
<li key={item} className="inline-flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
{item}
</li>
))}
</ul>
</article>
</section>
<section className="mt-10 rounded-2xl border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
<h2 className="mb-4 inline-flex items-center gap-2 text-2xl font-semibold">
<Cpu className="h-5 w-5 text-cyan-500" />
Setup notes for {config.name}
</h2>
<ol className="space-y-3 text-muted-foreground">
{config.setupNotes.map((item) => (
<li key={item} className="inline-flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
{item}
</li>
))}
</ol>
</section>
<section className="mt-10 rounded-2xl border border-zinc-200 bg-zinc-50 p-6 dark:border-zinc-700 dark:bg-zinc-900/70">
<h2 className="mb-4 inline-flex items-center gap-2 text-2xl font-semibold">
<CheckCircle2 className="h-5 w-5 text-green-500" />
Post-install checklist
</h2>
<ul className="space-y-3 text-muted-foreground">
{config.postInstallChecks.map((item) => (
<li key={item} className="inline-flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
{item}
</li>
))}
</ul>
<p className="mt-6 text-sm text-muted-foreground">
Need help selecting an engine? Read the
<Link href="/compare/" className="ml-1 font-semibold text-primary hover:underline">
whisper.cpp vs Whisper vs VOSK comparison
</Link>
.
</p>
</section>
</SeoSubpageShell>
);
}
+129
View File
@@ -0,0 +1,129 @@
import Link from "next/link";
import { type Metadata } from "next";
import { ChevronRight, Cpu, Laptop, Sparkles, Terminal } from "lucide-react";
import { SeoSubpageShell } from "@/components/seo-subpage-shell";
import { buildPageMetadata } from "@/lib/seo";
const installCommand =
"curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh --interactive";
const distroGuides = [
{
name: "Ubuntu",
href: "/install/ubuntu/",
description:
"Install offline voice dictation on Ubuntu 22.04+ with GNOME, KDE, X11, or Wayland.",
icon: Laptop,
accent: "from-orange-500/15 to-amber-500/10",
iconColor: "text-orange-500",
},
{
name: "Fedora",
href: "/install/fedora/",
description:
"Set up speech-to-text on Fedora Workstation and Silverblue with distro-specific notes.",
icon: Sparkles,
accent: "from-blue-500/15 to-cyan-500/10",
iconColor: "text-blue-500",
},
{
name: "Arch Linux",
href: "/install/arch/",
description:
"Install Vocalinux on Arch, Manjaro, and EndeavourOS with package prerequisites and tips.",
icon: Cpu,
accent: "from-emerald-500/15 to-teal-500/10",
iconColor: "text-emerald-500",
},
];
export const metadata: Metadata = buildPageMetadata({
title: "Install Voice Dictation on Linux (Ubuntu, Fedora, Arch)",
description:
"Step-by-step Linux voice dictation installation guides for Ubuntu, Fedora, and Arch. Install Vocalinux in minutes and start offline speech-to-text.",
path: "/install",
keywords: [
"install voice dictation linux",
"linux speech to text setup",
"ubuntu voice typing install",
"fedora speech recognition",
"arch linux dictation",
],
});
export default function InstallGuidesPage() {
return (
<SeoSubpageShell>
<section className="text-center">
<p className="mb-4 inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/10 px-4 py-1.5 text-sm font-medium text-primary">
<Sparkles className="h-4 w-4" />
Linux Installation Guides
</p>
<h1 className="mb-6 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
Install Voice Dictation on Linux
</h1>
<p className="mx-auto mb-8 max-w-3xl text-lg text-muted-foreground">
Pick your distro guide and follow a proven setup flow for offline speech-to-text on Linux.
Every guide is optimized for real desktop usage, not just terminal demos.
</p>
<div className="rounded-2xl border border-zinc-200 bg-zinc-950 p-6 text-left shadow-lg dark:border-zinc-700">
<p className="mb-3 inline-flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-zinc-400">
<Terminal className="h-4 w-4 text-green-400" />
Recommended interactive install
</p>
<code className="block overflow-x-auto whitespace-pre-wrap break-all text-sm text-green-400 sm:text-base">
{installCommand}
</code>
</div>
</section>
<section className="mt-14 grid gap-6 md:grid-cols-3">
{distroGuides.map((guide) => {
const Icon = guide.icon;
return (
<Link
key={guide.href}
href={guide.href}
className="group rounded-2xl border border-zinc-200 bg-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:shadow-lg dark:border-zinc-700 dark:bg-zinc-800"
>
<div
className={`mb-4 inline-flex rounded-xl bg-gradient-to-br p-3 ${guide.accent}`}
>
<Icon className={`h-6 w-6 ${guide.iconColor}`} />
</div>
<h2 className="mb-3 text-2xl font-semibold">{guide.name}</h2>
<p className="mb-5 text-sm text-muted-foreground">{guide.description}</p>
<span className="inline-flex items-center gap-1 text-sm font-semibold text-primary group-hover:underline">
Read {guide.name} guide
<ChevronRight className="h-4 w-4" />
</span>
</Link>
);
})}
</section>
<section className="mt-16 rounded-2xl border border-zinc-200 bg-zinc-50 p-8 dark:border-zinc-700 dark:bg-zinc-900/60">
<h2 className="mb-4 text-2xl font-bold">After Installation</h2>
<ul className="space-y-3 text-muted-foreground">
<li>
Launch with
<code className="ml-1.5 rounded bg-zinc-200 px-1.5 py-0.5 dark:bg-zinc-800">vocalinux</code>.
</li>
<li>Double-tap Ctrl to start or stop dictation in any text field.</li>
<li>
Choose whisper.cpp for speed, Whisper for maximum model compatibility, or VOSK for
low-RAM systems.
</li>
</ul>
<p className="mt-6 text-sm text-muted-foreground">
Need help choosing an engine? See the
<Link href="/compare/" className="ml-1 font-semibold text-primary hover:underline">
speech engine comparison
</Link>
.
</p>
</section>
</SeoSubpageShell>
);
}
+99 -221
View File
@@ -4,6 +4,14 @@ import { GeistSans } from "geist/font/sans";
import { type Metadata } from "next";
import Script from "next/script";
import { ThemeProvider } from "@/components/theme-provider";
import {
absoluteUrl,
DEFAULT_OG_IMAGE_ALT,
DEFAULT_OG_IMAGE_PATH,
SITE_NAME,
SITE_URL,
TWITTER_HANDLE,
} from "@/lib/seo";
export const viewport = {
width: "device-width",
@@ -16,36 +24,31 @@ export const viewport = {
};
export const metadata: Metadata = {
metadataBase: new URL("https://vocalinux.com"),
metadataBase: new URL(SITE_URL),
title: {
default: "Vocalinux - Voice Dictation for Linux, Finally Done Right",
template: "%s | Vocalinux"
default: "Vocalinux: Offline Voice Dictation for Linux",
template: "%s | Vocalinux",
},
description: "Free, open-source voice dictation for Linux. 100% offline with Whisper AI. Double-tap Ctrl to start. Works everywhere - X11 & Wayland. One-click install.",
applicationName: "Vocalinux",
description:
"Free and open-source voice dictation for Linux. Convert speech to text offline with whisper.cpp and VOSK on Ubuntu, Fedora, Arch, X11, and Wayland.",
applicationName: SITE_NAME,
keywords: [
"voice dictation linux",
"speech to text linux",
"linux voice typing",
"whisper linux",
"vosk linux",
"offline voice dictation",
"privacy voice recognition",
"speech recognition linux",
"dictation software ubuntu",
"voice typing fedora",
"linux voice typing",
"whisper.cpp linux",
"vosk linux",
"wayland voice dictation",
"x11 speech to text",
"open source dictation",
"free speech recognition",
"linux accessibility",
"hands free typing linux",
"voice commands linux",
"GTK voice dictation"
"x11 speech recognition",
"open source dictation software",
"voice typing ubuntu",
"voice typing fedora",
"voice typing arch linux",
],
authors: [{ name: "Jatin K Malik", url: "https://github.com/jatinkrmalik" }],
creator: "Jatin K Malik",
publisher: "Vocalinux",
publisher: SITE_NAME,
icons: {
icon: [
{ url: "/vocalinux.png", type: "image/png" },
@@ -54,37 +57,45 @@ export const metadata: Metadata = {
{ url: "/favicon-96x96.png", sizes: "96x96", type: "image/png" },
],
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
shortcut: "/vocalinux.png"
shortcut: "/vocalinux.png",
},
manifest: "/site.webmanifest",
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "Vocalinux"
title: SITE_NAME,
},
formatDetection: {
telephone: false
telephone: false,
email: false,
address: false,
},
referrer: "strict-origin-when-cross-origin",
openGraph: {
type: "website",
locale: "en_US",
url: "https://vocalinux.com",
siteName: "Vocalinux",
title: "Vocalinux - Voice Dictation for Linux, Finally Done Right",
description: "Free, open-source voice dictation for Linux. 100% offline & private with Whisper AI and VOSK. Double-tap Ctrl to start dictating in any app.",
images: [{
url: "/og-image.png",
width: 1200,
height: 630,
alt: "Vocalinux - Voice Dictation for Linux"
}]
url: absoluteUrl("/"),
siteName: SITE_NAME,
title: "Vocalinux: Offline Voice Dictation for Linux",
description:
"Voice dictation for Linux that runs fully offline. Install in minutes and dictate in any app on Ubuntu, Fedora, Arch, X11, or Wayland.",
images: [
{
url: DEFAULT_OG_IMAGE_PATH,
width: 1200,
height: 630,
alt: DEFAULT_OG_IMAGE_ALT,
},
],
},
twitter: {
card: "summary_large_image",
title: "Vocalinux - Voice Dictation for Linux, Finally Done Right",
description: "Free, open-source voice dictation for Linux. 100% offline & private. Double-tap Ctrl to start dictating in any app.",
images: ["/og-image.png"],
creator: "@jatinkrmalik"
title: "Vocalinux: Offline Voice Dictation for Linux",
description:
"Speech-to-text for Linux with whisper.cpp and VOSK. 100% offline, privacy-first, and open-source.",
images: [DEFAULT_OG_IMAGE_PATH],
creator: TWITTER_HANDLE,
site: TWITTER_HANDLE,
},
robots: {
index: true,
@@ -98,206 +109,73 @@ export const metadata: Metadata = {
},
},
alternates: {
canonical: "https://vocalinux.com",
canonical: absoluteUrl("/"),
languages: {
"en-US": absoluteUrl("/"),
},
},
category: "Technology",
};
const organizationJsonLd = {
"@context": "https://schema.org",
"@type": "Organization",
"@id": `${absoluteUrl("/")}#organization`,
name: SITE_NAME,
url: absoluteUrl("/"),
logo: absoluteUrl("/vocalinux.png"),
description: "Open-source, offline voice dictation software for Linux.",
sameAs: ["https://github.com/jatinkrmalik/vocalinux"],
founder: {
"@type": "Person",
name: "Jatin K Malik",
url: "https://github.com/jatinkrmalik",
},
};
const webSiteJsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
"@id": `${absoluteUrl("/")}#website`,
name: SITE_NAME,
url: absoluteUrl("/"),
inLanguage: "en-US",
description:
"Vocalinux provides offline speech-to-text and voice dictation for Linux desktops.",
publisher: {
"@id": `${absoluteUrl("/")}#organization`,
},
potentialAction: {
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: "https://github.com/jatinkrmalik/vocalinux/issues?q={search_term_string}",
},
"query-input": {
"@type": "PropertyValueSpecification",
valueRequired: true,
valueName: "search_term_string",
},
},
};
const siteWideJsonLd = [organizationJsonLd, webSiteJsonLd];
export default function RootLayout({
children
children,
}: Readonly<{
children: React.ReactNode;
}>) {
// SoftwareApplication schema
const softwareAppJsonLd = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Vocalinux",
"applicationCategory": "UtilitiesApplication",
"operatingSystem": "Linux",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"description": "Free, open-source voice dictation for Linux. 100% offline with Whisper AI and VOSK. Works with X11 and Wayland.",
"softwareVersion": "0.6.1-beta",
"author": {
"@type": "Person",
"name": "Jatin K Malik",
"url": "https://github.com/jatinkrmalik"
},
"url": "https://vocalinux.com",
"downloadUrl": "https://github.com/jatinkrmalik/vocalinux",
"screenshot": "https://vocalinux.com/og-image.png",
"featureList": [
"100% offline voice recognition",
"Privacy-focused - no cloud dependencies",
"Works with all Linux applications",
"X11 and Wayland support",
"Double-tap Ctrl activation",
"Whisper AI and VOSK engines",
"Voice commands for punctuation",
"System tray integration"
]
};
// Organization schema
const organizationJsonLd = {
"@context": "https://schema.org",
"@type": "Organization",
"name": "Vocalinux",
"url": "https://vocalinux.com",
"logo": "https://vocalinux.com/vocalinux.png",
"description": "Free, open-source voice dictation for Linux",
"sameAs": [
"https://github.com/jatinkrmalik/vocalinux"
],
"author": {
"@type": "Person",
"name": "Jatin K Malik",
"url": "https://github.com/jatinkrmalik"
}
};
// WebSite schema
const webSiteJsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Vocalinux",
"url": "https://vocalinux.com",
"description": "Voice dictation for Linux, finally done right. 100% offline and privacy-focused.",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://github.com/jatinkrmalik/vocalinux/issues?q={search_term_string}"
},
"query-input": {
"@type": "PropertyValueSpecification",
"valueRequired": true,
"valueName": "search_term_string"
}
}
};
// FAQPage schema for rich results
const faqJsonLd = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is Vocalinux really 100% offline?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes! All speech recognition processing happens locally on your machine. Your voice data never leaves your computer. No internet connection is required after installation."
}
},
{
"@type": "Question",
"name": "Which Linux distributions are supported?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Vocalinux works on most modern Linux distributions including Ubuntu 22.04+, Fedora, Debian, Arch Linux, Linux Mint, Pop!_OS, and more. It supports both X11 and Wayland display servers."
}
},
{
"@type": "Question",
"name": "How do I switch between Whisper and VOSK?",
"acceptedAnswer": {
"@type": "Answer",
"text": "You can switch engines via the settings GUI or command line. Use 'vocalinux --engine whisper' or 'vocalinux --engine vosk'. Whisper is recommended for accuracy, VOSK for speed."
}
},
{
"@type": "Question",
"name": "What are the system requirements?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Minimum: 4GB RAM, dual-core CPU, Python 3.8+. For Whisper large models: 8GB+ RAM recommended. The tiny/base Whisper models and VOSK work great on modest hardware."
}
},
{
"@type": "Question",
"name": "Can I use it in languages other than English?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes! Whisper supports 99+ languages with varying accuracy levels. VOSK has models for 20+ languages. Download additional language models as needed."
}
},
{
"@type": "Question",
"name": "How do I customize the activation shortcut?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The default is double-tap Ctrl. You can customize this via the GUI settings dialog or by editing ~/.config/vocalinux/config.yaml. Any key combination is supported."
}
},
{
"@type": "Question",
"name": "Is Vocalinux free?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes, Vocalinux is completely free and open-source, licensed under GPL-3.0. No premium tiers, no subscriptions, no tracking — just free software."
}
}
]
};
// HowTo schema for installation guide
const howToJsonLd = {
"@context": "https://schema.org",
"@type": "HowTo",
"name": "How to Install Vocalinux on Linux",
"description": "Step-by-step guide to install Vocalinux voice dictation software on Linux",
"totalTime": "PT10M",
"estimatedCost": {
"@type": "MonetaryAmount",
"currency": "USD",
"value": "0"
},
"step": [
{
"@type": "HowToStep",
"name": "Run the installation command",
"text": "Open your terminal and run the one-liner installation command to download and install Vocalinux.",
"url": "https://vocalinux.com/#install"
},
{
"@type": "HowToStep",
"name": "Wait for installation to complete",
"text": "The installer will download dependencies, set up the environment, and download speech recognition models. This takes approximately 5-10 minutes.",
"url": "https://vocalinux.com/#install"
},
{
"@type": "HowToStep",
"name": "Launch Vocalinux",
"text": "After installation, launch Vocalinux from your terminal by typing 'vocalinux' or find it in your application menu.",
"url": "https://vocalinux.com/#install"
},
{
"@type": "HowToStep",
"name": "Start dictating",
"text": "Double-tap Ctrl to start voice dictation. Speak clearly into your microphone. Double-tap Ctrl again to stop.",
"url": "https://vocalinux.com/#install"
}
]
};
const jsonLd = [softwareAppJsonLd, organizationJsonLd, webSiteJsonLd, faqJsonLd, howToJsonLd];
return (
<html lang="en" className={`${GeistSans.variable} scroll-smooth`} suppressHydrationWarning>
<head>
{/* Preconnect to GitHub API for faster fetch of stars and release info (~300ms improvement) */}
<link rel="preconnect" href="https://api.github.com" />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
dangerouslySetInnerHTML={{ __html: JSON.stringify(siteWideJsonLd) }}
/>
</head>
<body>
{/* Google tag (gtag.js) - deferred to prioritize page interactivity */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-7NBBNJNNQ7"
strategy="lazyOnload"
+205 -7
View File
@@ -5,7 +5,6 @@ import { motion } from "framer-motion";
import Link from "next/link";
import { LiveDemo } from "@/components/live-demo";
import {
Mic,
Terminal,
Lock,
Zap,
@@ -14,7 +13,6 @@ import {
Settings,
Code,
Github,
Edit,
Star,
Download,
ChevronRight,
@@ -30,7 +28,6 @@ import {
Volume2,
Sparkles,
Heart,
ExternalLink,
} from "lucide-react";
import { ThemeToggle } from "@/components/theme-toggle";
import { VocalinuxLogo } from "@/components/optimized-image";
@@ -51,6 +48,99 @@ const getInstallCommands = (latestRelease: string) => ({
uninstallCommand: `curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/${latestRelease}/uninstall.sh -o /tmp/vul.sh && bash /tmp/vul.sh`,
});
const homeJsonLd = [
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"@id": "https://vocalinux.com/#softwareapplication",
name: "Vocalinux",
applicationCategory: "UtilitiesApplication",
operatingSystem: "Linux",
isAccessibleForFree: true,
license: "https://github.com/jatinkrmalik/vocalinux/blob/main/LICENSE",
offers: {
"@type": "Offer",
price: "0",
priceCurrency: "USD",
},
description:
"Offline voice dictation and speech-to-text for Linux with whisper.cpp and VOSK.",
"softwareVersion": "0.6.1-beta",
author: {
"@type": "Person",
name: "Jatin K Malik",
url: "https://github.com/jatinkrmalik",
},
url: "https://vocalinux.com/",
downloadUrl: "https://github.com/jatinkrmalik/vocalinux",
screenshot: "https://vocalinux.com/og-image.png",
featureList: [
"100% offline speech recognition",
"Works with X11 and Wayland",
"Double-tap Ctrl activation",
"whisper.cpp, Whisper, and VOSK support",
"Linux desktop integration",
],
},
{
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: [
{
"@type": "Question",
name: "Is Vocalinux really 100% offline?",
acceptedAnswer: {
"@type": "Answer",
text: "Yes. All speech recognition runs locally on your Linux machine. Voice data does not leave your computer.",
},
},
{
"@type": "Question",
name: "Which Linux distributions are supported?",
acceptedAnswer: {
"@type": "Answer",
text: "Vocalinux supports Ubuntu, Fedora, Debian, Arch, Linux Mint, Pop!_OS, and other modern Linux distributions across X11 and Wayland.",
},
},
{
"@type": "Question",
name: "How do I switch between speech engines?",
acceptedAnswer: {
"@type": "Answer",
text: "Use the settings GUI or the CLI flags: vocalinux --engine whisper_cpp, vocalinux --engine whisper, or vocalinux --engine vosk.",
},
},
],
},
{
"@context": "https://schema.org",
"@type": "HowTo",
name: "Install Vocalinux on Linux",
description: "Install offline voice dictation on Linux in a few minutes.",
totalTime: "PT10M",
step: [
{
"@type": "HowToStep",
name: "Run the install command",
text: "Copy the install command from vocalinux.com and run it in terminal.",
url: "https://vocalinux.com/#install",
},
{
"@type": "HowToStep",
name: "Complete the guided setup",
text: "Pick your speech engine and model size in the interactive installer.",
url: "https://vocalinux.com/#install",
},
{
"@type": "HowToStep",
name: "Start dictating",
text: "Launch vocalinux and double-tap Ctrl to begin voice dictation.",
url: "https://vocalinux.com/#install",
},
],
},
];
const FeatureCard = ({
icon,
title,
@@ -173,6 +263,7 @@ export default function HomePage() {
{ href: "#demo", label: "Demo" },
{ href: "#features", label: "Features" },
{ href: "#install", label: "Install" },
{ href: "#guides", label: "Guides" },
{ href: "#faq", label: "FAQ" },
];
@@ -182,6 +273,11 @@ export default function HomePage() {
return (
<main className="min-h-screen bg-gradient-to-b from-zinc-50 to-white dark:from-zinc-900 dark:to-zinc-950">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(homeJsonLd) }}
/>
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-md border-b border-border">
<nav className="max-w-7xl mx-auto px-4 sm:px-6 py-3 flex items-center justify-between">
@@ -439,7 +535,7 @@ export default function HomePage() {
<FadeInSection>
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">
Try It Yourself
Try Linux Speech-to-Text in Your Browser
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Experience voice-to-text right here in your browser. Click the microphone and start speaking!
@@ -493,7 +589,7 @@ export default function HomePage() {
<FadeInSection>
<div className="text-center mb-16">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">
Why Choose Vocalinux?
Offline Voice Dictation Features for Linux
</h2>
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
Finally, Linux users get the voice dictation experience they deserve —
@@ -598,7 +694,7 @@ export default function HomePage() {
<FadeInSection>
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">
Get Started in Seconds
How to Install Voice Dictation on Linux
</h2>
<p className="text-lg text-muted-foreground">
One command. That&apos;s all it takes.
@@ -769,13 +865,80 @@ export default function HomePage() {
</div>
</section>
{/* SEO Guide Hub Section */}
<section id="guides" className="py-16 sm:py-24 px-4 sm:px-6">
<div className="max-w-6xl mx-auto">
<FadeInSection>
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">
Linux Voice Dictation Guides by Distribution
</h2>
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
Follow distro-specific setup instructions for Ubuntu, Fedora, and Arch Linux.
These pages are written for real desktop workflows and include post-install checks.
</p>
</div>
</FadeInSection>
<div className="grid gap-6 md:grid-cols-3">
{[
{
href: "/install/ubuntu/",
title: "Ubuntu Voice Typing Guide",
description:
"Install offline speech-to-text on Ubuntu 22.04+ for GNOME, KDE, X11, and Wayland.",
},
{
href: "/install/fedora/",
title: "Fedora Speech-to-Text Guide",
description:
"Set up Vocalinux on Fedora Workstation with distro-specific notes for a stable dictation flow.",
},
{
href: "/install/arch/",
title: "Arch Linux Dictation Guide",
description:
"Lean setup for Arch, Manjaro, and EndeavourOS with hardware and injection sanity checks.",
},
].map((guide) => (
<Link
key={guide.href}
href={guide.href}
className="group rounded-2xl border border-zinc-200 bg-white p-6 shadow-sm transition-all hover:-translate-y-1 hover:shadow-lg dark:border-zinc-700 dark:bg-zinc-800"
>
<h3 className="mb-3 text-xl font-semibold">{guide.title}</h3>
<p className="mb-4 text-sm text-muted-foreground">{guide.description}</p>
<span className="inline-flex items-center gap-1 text-sm font-semibold text-primary group-hover:underline">
Read guide <ChevronRight className="h-4 w-4" />
</span>
</Link>
))}
</div>
<FadeInSection delay={0.15}>
<div className="mt-8 rounded-2xl border border-primary/20 bg-primary/5 p-6 sm:p-8">
<h3 className="text-2xl font-bold mb-3">Need help choosing an engine?</h3>
<p className="text-muted-foreground mb-4">
Compare whisper.cpp, Whisper, and VOSK for speed, hardware support, and model size.
</p>
<Link
href="/compare/"
className="inline-flex items-center gap-2 text-sm font-semibold text-primary hover:underline"
>
View speech engine comparison <ChevronRight className="h-4 w-4" />
</Link>
</div>
</FadeInSection>
</div>
</section>
{/* Speech Engines Section */}
<section className="py-16 sm:py-24 px-4 sm:px-6 bg-zinc-50 dark:bg-zinc-900/50">
<div className="max-w-5xl mx-auto">
<FadeInSection>
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">
Choose Your Engine
Choose Your Linux Speech Recognition Engine
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Vocalinux supports multiple speech recognition engines. Pick the one that suits your needs.
@@ -1023,6 +1186,31 @@ export default function HomePage() {
FAQ
</a>
</li>
<li>
<Link href="/install/" className="text-zinc-400 hover:text-white transition-colors">
Linux Install Guides
</Link>
</li>
<li>
<Link href="/install/ubuntu/" className="text-zinc-400 hover:text-white transition-colors">
Ubuntu Guide
</Link>
</li>
<li>
<Link href="/install/fedora/" className="text-zinc-400 hover:text-white transition-colors">
Fedora Guide
</Link>
</li>
<li>
<Link href="/install/arch/" className="text-zinc-400 hover:text-white transition-colors">
Arch Guide
</Link>
</li>
<li>
<Link href="/compare/" className="text-zinc-400 hover:text-white transition-colors">
Engine Comparison
</Link>
</li>
</ul>
</div>
@@ -1059,6 +1247,16 @@ export default function HomePage() {
Discussions
</a>
</li>
<li>
<Link href="/install/" className="text-zinc-400 hover:text-white transition-colors">
Setup by Distribution
</Link>
</li>
<li>
<Link href="/compare/" className="text-zinc-400 hover:text-white transition-colors">
whisper.cpp vs Whisper vs VOSK
</Link>
</li>
<li>
<a
href="https://github.com/jatinkrmalik/vocalinux/blob/main/CONTRIBUTING.md"
+91
View File
@@ -0,0 +1,91 @@
import React from "react";
import Link from "next/link";
import { BookOpen, ChevronRight, Cpu, Home } from "lucide-react";
import { VocalinuxLogo } from "@/components/optimized-image";
interface SeoSubpageShellProps {
children: React.ReactNode;
}
const navLinks = [
{
href: "/",
label: "Home",
icon: Home,
},
{
href: "/install/",
label: "Install Guides",
icon: BookOpen,
},
{
href: "/compare/",
label: "Engine Compare",
icon: Cpu,
},
];
export function SeoSubpageShell({ children }: SeoSubpageShellProps) {
return (
<div className="min-h-screen bg-gradient-to-b from-zinc-50 to-white dark:from-zinc-900 dark:to-zinc-950">
<header className="border-b border-zinc-200/80 bg-background/80 backdrop-blur-md dark:border-zinc-800/80">
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-4 sm:px-6">
<Link href="/" className="group flex items-center gap-2">
<VocalinuxLogo width={30} height={30} className="h-7 w-7 transition-transform group-hover:scale-110" />
<span className="text-base font-bold sm:text-lg">Vocalinux</span>
</Link>
<nav className="hidden items-center gap-2 sm:flex">
{navLinks.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className="inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-zinc-100 hover:text-foreground dark:hover:bg-zinc-800"
>
<Icon className="h-4 w-4" />
{item.label}
</Link>
);
})}
</nav>
</div>
<div className="mx-auto flex max-w-6xl gap-2 px-4 pb-4 sm:hidden">
{navLinks.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className="inline-flex items-center gap-1 rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-xs font-medium text-muted-foreground dark:border-zinc-700 dark:bg-zinc-800"
>
<Icon className="h-3.5 w-3.5" />
{item.label}
</Link>
);
})}
</div>
</header>
<div className="mx-auto w-full max-w-6xl px-4 pb-16 pt-10 sm:px-6 sm:pt-12">{children}</div>
<footer className="border-t border-zinc-200 bg-white/70 dark:border-zinc-800 dark:bg-zinc-900/60">
<div className="mx-auto flex max-w-6xl flex-col items-start justify-between gap-5 px-4 py-8 sm:flex-row sm:items-center sm:px-6">
<div>
<p className="text-sm font-semibold">Want the full product overview?</p>
<p className="text-sm text-muted-foreground">Go back to the homepage for the live demo and one-command installer.</p>
</div>
<Link
href="/"
className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground hover:bg-primary/90"
>
Home Page
<ChevronRight className="h-4 w-4" />
</Link>
</div>
</footer>
</div>
);
}
+72
View File
@@ -0,0 +1,72 @@
import { type Metadata } from "next";
export const SITE_URL = "https://vocalinux.com";
export const SITE_NAME = "Vocalinux";
export const DEFAULT_OG_IMAGE_PATH = "/og-image.png";
export const DEFAULT_OG_IMAGE_ALT = "Vocalinux - Voice Dictation for Linux";
export const TWITTER_HANDLE = "@jatinkrmalik";
interface PageMetadataOptions {
title: string;
description: string;
path?: string;
keywords?: string[];
}
const normalizePath = (path: string): string => {
if (path === "/") {
return "/";
}
const trimmed = path.replace(/^\/+|\/+$/g, "");
return `/${trimmed}/`;
};
export const absoluteUrl = (path = "/"): string => {
return new URL(normalizePath(path), SITE_URL).toString();
};
export const buildPageMetadata = ({
title,
description,
path = "/",
keywords = [],
}: PageMetadataOptions): Metadata => {
const canonical = absoluteUrl(path);
return {
title,
description,
keywords,
alternates: {
canonical,
languages: {
"en-US": canonical,
},
},
openGraph: {
type: "website",
locale: "en_US",
siteName: SITE_NAME,
url: canonical,
title,
description,
images: [
{
url: DEFAULT_OG_IMAGE_PATH,
width: 1200,
height: 630,
alt: DEFAULT_OG_IMAGE_ALT,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [DEFAULT_OG_IMAGE_PATH],
creator: TWITTER_HANDLE,
site: TWITTER_HANDLE,
},
};
};