feat: improve SEO with favicons, optimized OG image, and enhanced schema (#99)
* feat: improve SEO with favicons, optimized OG image, and enhanced schema - Add favicon PNG assets (16x16, 32x32, 96x96) for better browser/Search display - Add apple-touch-icon (180x180) for iOS devices - Add PWA icons (192x192, 512x512) with maskable support - Optimize OG image from 6.8MB to 279KB (1200x630px) - Update web manifest with correct icons and theme color (#3CB371) - Enhance JSON-LD with Organization and WebSite schemas - Remove hardcoded version from SoftwareApplication schema - Update sitemap lastmod to 2026-01-29 and changefreq to monthly * fix: add back softwareVersion for CI sync * feat: regenerate favicons with high-res transparent image * feat: replace vocalinux.svg with vocalinux.png in web
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 6.5 MiB After Width: | Height: | Size: 273 KiB |
@@ -5,7 +5,7 @@
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#7c3aed",
|
||||
"theme_color": "#3CB371",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-16x16.png",
|
||||
@@ -17,10 +17,27 @@
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://vocalinux.com/</loc>
|
||||
<lastmod>2026-01-19</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<lastmod>2026-01-29</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
|
||||
|
After Width: | Height: | Size: 207 KiB |
@@ -48,10 +48,13 @@ export const metadata: Metadata = {
|
||||
publisher: "Vocalinux",
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: "/vocalinux.svg", type: "image/svg+xml" },
|
||||
{ url: "/vocalinux.png", type: "image/png" },
|
||||
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
||||
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
||||
{ url: "/favicon-96x96.png", sizes: "96x96", type: "image/png" },
|
||||
],
|
||||
apple: [{ url: "/vocalinux.svg", type: "image/svg+xml" }],
|
||||
shortcut: "/vocalinux.svg"
|
||||
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
|
||||
shortcut: "/vocalinux.png"
|
||||
},
|
||||
manifest: "/site.webmanifest",
|
||||
appleWebApp: {
|
||||
@@ -104,7 +107,8 @@ export default function RootLayout({
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const jsonLd = {
|
||||
// SoftwareApplication schema
|
||||
const softwareAppJsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Vocalinux",
|
||||
@@ -116,7 +120,7 @@ export default function RootLayout({
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"description": "Free, open-source voice dictation for Linux. 100% offline with Whisper AI and VOSK. Works with X11 and Wayland.",
|
||||
"softwareVersion": "0.4.1-alpha",
|
||||
"softwareVersion": "0.0.0",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Jatin K Malik",
|
||||
@@ -137,6 +141,47 @@ export default function RootLayout({
|
||||
]
|
||||
};
|
||||
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const jsonLd = [softwareAppJsonLd, organizationJsonLd, webSiteJsonLd];
|
||||
|
||||
return (
|
||||
<html lang="en" className={`${GeistSans.variable} scroll-smooth`} suppressHydrationWarning>
|
||||
<head>
|
||||
|
||||
@@ -195,7 +195,7 @@ export default function HomePage() {
|
||||
<nav className="max-w-7xl mx-auto px-4 sm:px-6 py-3 flex items-center justify-between">
|
||||
<Link href="/" className="flex items-center gap-2 group">
|
||||
<Image
|
||||
src="/vocalinux.svg"
|
||||
src="/vocalinux.png"
|
||||
alt="Vocalinux Logo"
|
||||
width={32}
|
||||
height={32}
|
||||
@@ -557,7 +557,7 @@ export default function HomePage() {
|
||||
<div className="relative">
|
||||
<div className="bg-white dark:bg-zinc-800 rounded-2xl p-8 shadow-xl">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Image src="/vocalinux.svg" alt="Vocalinux" width={64} height={64} className="h-16 w-16" />
|
||||
<Image src="/vocalinux.png" alt="Vocalinux" width={64} height={64} className="h-16 w-16" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">Vocalinux</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
@@ -1026,7 +1026,7 @@ export default function HomePage() {
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-8 mb-12">
|
||||
<div className="md:col-span-2">
|
||||
<Link href="/" className="flex items-center gap-2 mb-4">
|
||||
<Image src="/vocalinux.svg" alt="Vocalinux" width={32} height={32} className="h-8 w-8" />
|
||||
<Image src="/vocalinux.png" alt="Vocalinux" width={32} height={32} className="h-8 w-8" />
|
||||
<span className="text-xl font-bold">Vocalinux</span>
|
||||
</Link>
|
||||
<p className="text-zinc-400 mb-4 max-w-md">
|
||||
|
||||
@@ -7,7 +7,7 @@ import Image from "next/image";
|
||||
export const VocalinuxLogo: React.FC<{ className?: string }> = ({ className = "" }) => {
|
||||
return (
|
||||
<Image
|
||||
src="/vocalinux.svg"
|
||||
src="/vocalinux.png"
|
||||
alt="Vocalinux Logo"
|
||||
width={36}
|
||||
height={36}
|
||||
|
||||