chore(website): enhance SEO with structured data and meta tags in various components

This commit is contained in:
4gray committed 2026-02-15 01:22:59 +01:00
1 parent bdf0e3265a
commit 7931127aa6
9 files changed
+172 -12

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/4gray/iptvnator/build-and-test.yaml?style=for-the-badge&logo=github"> <a href="https://github.com/4gray/iptvnator/releases"><img src="https://img.shields.io/github/downloads/4gray/iptvnator/total?style=for-the-badge&logo=github" alt="Releases"></a> <a href="https://codecov.io/gh/4gray/iptvnator"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/4gray/iptvnator?style=for-the-badge"></a> <a href="https://t.me/iptvnator"><img src="https://img.shields.io/badge/telegram-iptvnator-blue?logo=telegram&style=for-the-badge" alt="Telegram"></a> <a href="https://bsky.app/profile/iptvnator.bsky.social"><img src="https://img.shields.io/badge/bluesky-iptvnator-darkblue?logo=bluesky&style=for-the-badge" alt="Bluesky"></a>
</p>
<a href="https://t.me/iptvnator">Telegram channel for discussions</a> | <a href="https://ko-fi.com/4gray" target="_blank">Buy me a coffee</a> | <a href="https://github.com/sponsors/4gray">GitHub Sponsors</a>
🌐 **[Website](https://4gray.github.io/iptvnator/)** | <a href="https://t.me/iptvnator">Telegram channel for discussions</a> | <a href="https://ko-fi.com/4gray" target="_blank">Buy me a coffee</a> | <a href="https://github.com/sponsors/4gray">GitHub Sponsors</a>
**IPTVnator** is a video player application that provides support for IPTV playlist playback (m3u, m3u8). The application allows users to import playlists using remote URLs or by uploading files from the local file system. Additionally, it supports EPG information in XMLTV format which can be provided via URL.
+26
View File
@@ -0,0 +1,26 @@
# IPTVnator
> IPTVnator is an open-source cross-platform IPTV player (desktop + web) that supports M3U/M3U8 playlists, Xtream Codes API, Stalker portals, and XMLTV EPG.
## Canonical URLs
- Website: https://4gray.github.io/iptvnator/
- Blog: https://4gray.github.io/iptvnator/blog/
- Latest Release: https://github.com/4gray/iptvnator/releases/latest
- Release Notes (v0.18): https://4gray.github.io/iptvnator/blog/v0-18-release-notes/
- Troubleshooting (macOS app damaged fix): https://4gray.github.io/iptvnator/blog/macos-arm-app-damaged-fix/
- Source Code: https://github.com/4gray/iptvnator
- Telegram: https://t.me/iptvnator
- GitHub Sponsors: https://github.com/sponsors/4gray
- Ko-fi: https://ko-fi.com/4gray
## Product Scope
- IPTVnator is a media player application.
- It does not provide, host, or sell playlists, channels, or media content.
- Any channel names or metadata shown in screenshots/posts are demo examples only.
## Machine-readable Discovery
- Sitemap: https://4gray.github.io/iptvnator/sitemap-index.xml
- Robots: https://4gray.github.io/iptvnator/robots.txt
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://4gray.github.io/iptvnator/sitemap-index.xml
+46 -7
View File
@@ -3,16 +3,46 @@ interface Props {
title: string;
description: string;
image?: string;
type?: 'website' | 'article';
noindex?: boolean;
keywords?: string[];
publishedTime?: string;
modifiedTime?: string;
jsonLd?: Record<string, unknown> | Array<Record<string, unknown>>;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = '/iptvnator/og-image.png' } = Astro.props;
const {
title,
description,
image = '/iptvnator/og-image.png',
type = 'website',
noindex = false,
keywords = [],
publishedTime,
modifiedTime,
jsonLd,
} = Astro.props;
const absoluteImageURL = new URL(image, Astro.site);
const robotsContent = noindex
? 'noindex, nofollow, noarchive'
: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';
const schemaPayload = jsonLd
? JSON.stringify(Array.isArray(jsonLd) ? jsonLd : [jsonLd])
: null;
---
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/iptvnator/favicon.ico" />
<meta name="generator" content={Astro.generator} />
<meta name="robots" content={robotsContent} />
<meta name="referrer" content="strict-origin-when-cross-origin" />
<meta name="author" content="4gray" />
<meta name="theme-color" content="#0a0a08" />
<meta name="format-detection" content="telephone=no" />
{keywords.length > 0 && <meta name="keywords" content={keywords.join(', ')} />}
<!-- Fonts: Playfair Display (display), DM Sans (body), IBM Plex Mono (code) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -23,7 +53,7 @@ const { title, description, image = '/iptvnator/og-image.png' } = Astro.props;
/>
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<link rel="canonical" href={canonicalURL.toString()} />
<!-- Primary Meta Tags -->
<title>{title}</title>
@@ -31,15 +61,24 @@ const { title, description, image = '/iptvnator/og-image.png' } = Astro.props;
<meta name="description" content={description} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:type" content={type} />
<meta property="og:site_name" content="IPTVnator" />
<meta property="og:locale" content="en_US" />
<meta property="og:url" content={canonicalURL.toString()} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} />
<meta property="og:image" content={absoluteImageURL.toString()} />
<meta property="og:image:secure_url" content={absoluteImageURL.toString()} />
{type === 'article' && publishedTime && <meta property="article:published_time" content={publishedTime} />}
{type === 'article' && modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:url" content={canonicalURL.toString()} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.url)} />
<meta property="twitter:image" content={absoluteImageURL.toString()} />
{schemaPayload && (
<script type="application/ld+json" set:html={schemaPayload} />
)}
+18 -2
View File
@@ -8,15 +8,31 @@ interface Props {
title: string;
description: string;
image?: string;
type?: 'website' | 'article';
noindex?: boolean;
keywords?: string[];
publishedTime?: string;
modifiedTime?: string;
jsonLd?: Record<string, unknown> | Array<Record<string, unknown>>;
}
const { title, description, image } = Astro.props;
const { title, description, image, type, noindex, keywords, publishedTime, modifiedTime, jsonLd } = Astro.props;
---
<!doctype html>
<html lang="en" class="dark">
<head>
<BaseHead title={title} description={description} image={image} />
<BaseHead
title={title}
description={description}
image={image}
type={type}
noindex={noindex}
keywords={keywords}
publishedTime={publishedTime}
modifiedTime={modifiedTime}
jsonLd={jsonLd}
/>
</head>
<body class="min-h-screen bg-surface-950 font-body text-surface-200">
<Header />
+35 -1
View File
@@ -26,9 +26,43 @@ const formattedUpdatedDate = updatedDate?.toLocaleDateString('en-US', {
});
const AUTHOR_AVATAR_URL = 'https://avatars.githubusercontent.com/u/1503032';
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
const blogPostSchema = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: title,
description,
datePublished: pubDate.toISOString(),
dateModified: (updatedDate ?? pubDate).toISOString(),
author: {
'@type': 'Person',
name: author,
url: 'https://github.com/4gray',
},
publisher: {
'@type': 'Organization',
name: 'IPTVnator',
url: 'https://4gray.github.io/iptvnator/',
logo: {
'@type': 'ImageObject',
url: 'https://4gray.github.io/iptvnator/favicon.ico',
},
},
mainEntityOfPage: canonicalURL,
image: heroImage ? new URL(heroImage, Astro.site).toString() : undefined,
};
---
<BaseLayout title={`${title} | IPTVnator Blog`} description={description}>
<BaseLayout
title={`${title} | IPTVnator Blog`}
description={description}
image={heroImage}
type="article"
publishedTime={pubDate.toISOString()}
modifiedTime={(updatedDate ?? pubDate).toISOString()}
keywords={tags}
jsonLd={blogPostSchema}
>
<article class="pt-36 pb-24">
<div class="relative mx-auto max-w-3xl px-6">
<div class="pointer-events-none absolute -top-16 -right-14 hidden w-44 rotate-[-8deg] opacity-30 mix-blend-screen md:block lg:w-52">
+5 -1
View File
@@ -2,7 +2,11 @@
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Page Not Found | IPTVnator" description="The page you're looking for doesn't exist.">
<BaseLayout
title="Page Not Found | IPTVnator"
description="The page you're looking for doesn't exist."
noindex={true}
>
<section class="flex min-h-[80vh] items-center justify-center pt-20">
<div class="mx-auto max-w-md px-6 text-center">
<img
+1
View File
@@ -17,6 +17,7 @@ const remainingPosts = featuredPost ? posts.filter((post) => post.id !== feature
<BaseLayout
title="Blog | IPTVnator"
description="Release notes, tutorials, and project updates from the IPTVnator team."
keywords={['IPTVnator blog', 'IPTV release notes', 'EPG, Archive setup', 'Xtream Codes Player (XC)', 'Stalker Player Support (STB)']}
>
<section class="pt-36 pb-24">
<div class="mx-auto max-w-6xl px-6">
+36
View File
@@ -18,11 +18,47 @@ const allPosts = (await getCollection('blog'))
const featuredPost = allPosts.find((post) => post.data.featured) ?? allPosts[0];
const otherPosts = featuredPost ? allPosts.filter((post) => post.id !== featuredPost.id).slice(0, 2) : [];
const posts = featuredPost ? [featuredPost, ...otherPosts] : allPosts.slice(0, 3);
const homeSchema = [
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'IPTVnator',
url: 'https://4gray.github.io/iptvnator/',
description:
'A cross-platform IPTV player supporting M3U/M3U8 playlists, Xtream Codes API, and Stalker portals.',
inLanguage: 'en',
},
{
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'IPTVnator',
applicationCategory: 'MultimediaApplication',
operatingSystem: 'Windows, macOS, Linux, Web',
softwareVersion: '0.19.0',
url: 'https://4gray.github.io/iptvnator/',
downloadUrl: 'https://github.com/4gray/iptvnator/releases/latest',
author: {
'@type': 'Person',
name: '4gray',
url: 'https://github.com/4gray',
},
sameAs: [
'https://github.com/4gray/iptvnator',
'https://t.me/iptvnator',
'https://ko-fi.com/4gray',
],
description:
'Open-source IPTV player with M3U/M3U8, Xtream Codes API, and Stalker portal support.',
},
];
---
<BaseLayout
title="IPTVnator - Open Source IPTV Player"
description="A cross-platform IPTV player supporting M3U/M3U8 playlists, Xtream Codes API, and Stalker portals. Available as a desktop app and PWA."
keywords={['IPTV player', 'M3U', 'M3U8', 'Xtream Codes', 'Stalker portal', 'EPG', 'XMLTV', 'open-source']}
jsonLd={homeSchema}
>
<Hero />
<FeatureGrid />