Publish /compare/ with three pages answering the choices the app asks users to make: M3U vs Xtream Codes vs Stalker portal, the built-in web players vs embedded MPV vs external MPV/VLC, and the desktop app vs the self-hosted browser version. Each opens with a one-paragraph verdict, carries a feature matrix whose cells are yes, no or a qualifier, and emits WebPage / FAQPage / BreadcrumbList JSON-LD — not SoftwareApplication, because guidance is not a product listing. These compare IPTVnator's own options against each other rather than naming other products, so every cell is checkable against this repository. Pages that name competitors remain phase 3's open half; the plan now records what has to be decided first. A registry in src/lib/comparisons.ts drives the hub, the switcher and the tests. The header gains a Compare entry and the features hub links across. tools/testing/website-compare-pages.test.mjs checks canonical URLs, the verdict block, the table, schema, cross-links and sitemap entries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.6 KiB
IPTVnator Website
The website is an Astro static site deployed to GitHub Pages at https://4gray.github.io/iptvnator/.
Blog Comments
Blog posts render Giscus comments from apps/website/src/components/GiscusComments.astro.
Giscus stores comments in GitHub Discussions for 4gray/iptvnator and maps each page to a discussion by pathname, including the GitHub Pages base path such as /iptvnator/blog/why-external-players-help/.
The embed is wired to the dedicated Blog comments discussion category:
- Repository id:
MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg= - Category id:
DIC_kwDODLUX8s4C9eBJ - Mapping:
pathname - Theme:
transparent_dark
If the category is recreated, query the new category id:
gh api graphql \
-f owner=4gray \
-f name=iptvnator \
-f query='query($owner:String!, $name:String!) { repository(owner:$owner, name:$name) { discussionCategories(first:25) { nodes { id name slug isAnswerable } } } }'
Then update data-category-id in GiscusComments.astro.
Moderation happens in GitHub Discussions. Maintainers can hide, delete, lock, or move discussions and comments from the repository Discussions UI.
Download Pages
/download/ plus /download/windows/, /download/macos/ and /download/linux/
are per-OS landing pages (apps/website/src/pages/download/). They exist for
search visibility on "IPTVnator download" style queries and to spare users
the 27-asset GitHub release page; each carries OS-specific install steps,
requirements, an FAQ and SoftwareApplication / FAQPage / BreadcrumbList
structured data. Shared pieces live in src/components/download/ and reuse the
blog components (StepRail, Alert, FaqAccordion, CopyCommand).
Latest-release resolution
Direct asset links need the release version, so src/lib/downloads.ts
resolves it at build time:
GET https://api.github.com/repos/4gray/iptvnator/releases/latest(8 s timeout). The asset list from the published release is authoritative: options whose file is missing are dropped, sizes and the publish date come from the API.deploy-website.ymlpassesGITHUB_TOKENto the build so the call is authenticated.- Fallback: the root
package.jsonversion with the asset naming pattern fromelectron-builder.json. This is deterministic but cannot prove the files exist yet (a version bump lands onmasterbefore the release is published), so a warning is printed. SetWEBSITE_SKIP_RELEASE_FETCH=1to force it for offline or reproducible builds.
Both paths produce the same page structure. Adding an artifact means adding a
DownloadOption (matcher + fallback name) in downloads.ts; the pages and the
hub pick it up. The homepage SoftwareApplication schema reads the same
resolved version.
pnpm nx test website builds the site and runs
tools/testing/website-download-pages.test.mjs, which checks titles,
canonicals, direct asset links, JSON-LD, cross-links and sitemap entries
without depending on a specific version.
Guides
Evergreen how-to posts live in the blog collection next to release notes
(xtream-codes-setup-guide.mdx, stalker-portal-setup-guide.mdx and
m3u-playlist-epg-setup-guide.mdx in apps/website/src/content/blog/).
Two conventions set them apart:
faqfrontmatter. An optional list of{ q, a }entries.BlogPost.astrorenders it as an accordion after the body and emits aFAQPageJSON-LD block next to theBlogPostingone, so the answers can surface as rich results.- Screenshots from the capture script. Guide frames are captured by
pnpm release:screenshots --group guidesintoapps/website/public/blog/guides/screenshots/<slug>-<theme>.png; the shots are declared intools/release/screenshots.manifest.jsonwith"group": "guides"and never appear in a release run.
tools/testing/website-guides.test.mjs (part of pnpm nx test website) checks
each guide for the FAQPage schema, a link to the download hub and the presence
of every referenced screenshot in the build output.
Feature Pages
/features/ plus one page per feature (m3u-player, xtream-codes-player,
stalker-portal-player, epg, remote-control) live in
apps/website/src/pages/features/. They target " player" style
searches, reuse the download-page sections, and each carries
SoftwareApplication (with featureList) / FAQPage / BreadcrumbList
structured data. The registry in src/lib/features.ts drives the hub, the
per-page switcher, the homepage feature cards and
tools/testing/website-feature-pages.test.mjs; adding a page means adding one
registry entry and one .astro file. Screenshots come only from the
mock-backed guide and release captures, never from the older homepage
screenshots that show real channel names.
Comparison Pages
/compare/ plus one page per decision the app asks users to make
(m3u-vs-xtream-vs-stalker, playback-engines, desktop-vs-browser) live in
apps/website/src/pages/compare/. They compare IPTVnator's own options against
each other, never other products, so every claim is checkable against this
repository; the registry is src/lib/comparisons.ts.
Each page opens with a one-paragraph verdict (CompareHero), carries at least
one ComparisonTable (cells are true, false or a qualifying string) and
emits WebPage / FAQPage / BreadcrumbList JSON-LD from
src/lib/comparison-schema.ts — deliberately not SoftwareApplication, since
these pages are guidance rather than a product listing, and
tools/testing/website-compare-pages.test.mjs asserts that.
Naming a competitor on these pages is a product decision, not a technical one.
Phase 3 of .plans/2026-09-03-marketing-landing-pages.md covers that and is
still open.