From ef1f06d465fe8bd13b1230104071b79a293b41d4 Mon Sep 17 00:00:00 2001 From: Renato Date: Tue, 4 Aug 2026 17:47:05 +0800 Subject: [PATCH] SEO: OG por post, RSS feed, sitemap, FTS5 busqueda, View Transitions, Cloudflare Polish --- src/app/(public)/layout.tsx | 13 +++++- src/app/(public)/p/[slug]/page.tsx | 37 ++++++++++++++++ src/app/layout.tsx | 32 +++++++++++++- src/app/rss.xml/route.ts | 48 +++++++++++++++++++++ src/app/sitemap.ts | 33 +++++++++++++++ src/lib/db.ts | 68 +++++++++++++++++++++++++----- src/middleware.ts | 2 + 7 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 src/app/rss.xml/route.ts create mode 100644 src/app/sitemap.ts diff --git a/src/app/(public)/layout.tsx b/src/app/(public)/layout.tsx index 162c159..978e2b1 100644 --- a/src/app/(public)/layout.tsx +++ b/src/app/(public)/layout.tsx @@ -1,5 +1,5 @@ import Link from "next/link" -import { Image, Search } from "lucide-react" +import { Image, Search, Rss } from "lucide-react" import { SITE_NAME, SITE_TAGLINE, SITE_LINKS } from "@/lib/site" import { AgeGate } from "@/components/age-gate" @@ -37,6 +37,17 @@ export default function PublicLayout({ children }: { children: React.ReactNode } {SITE_NAME} · {SITE_TAGLINE}

+ + + RSS + + | {SITE_LINKS.twitter && ( <> +}): Promise { + const { slug } = await params + const post = getPostBySlug(slug) + if (!post) return { title: "No encontrado" } + + const title = post.title + const description = + post.summary && !post.summary.startsWith("**") && post.summary.length > 40 + ? post.summary.slice(0, 160) + : `${post.num_pages} páginas${post.artist ? ` por ${post.artist}` : ""} — worst-scan fansub` + + return { + title, + description, + openGraph: { + title, + description, + type: "article", + url: `https://worstscan.xyz/p/${post.slug}`, + images: [{ url: `https://worstscan.xyz/api/cover/${post.gid}`, width: 1200, height: 1600, alt: title }], + }, + twitter: { + card: "summary_large_image", + title, + description, + images: [`https://worstscan.xyz/api/cover/${post.gid}`], + }, + } +} + export default async function PostDetailPage({ params, }: { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 34d55e4..b9813a1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,8 +2,25 @@ import type { Metadata } from "next" import "./globals.css" export const metadata: Metadata = { - title: "worst-scan", + title: { + default: "worst-scan", + template: "%s — worst-scan", + }, description: "worst-scan fansub — escaneos y traducciones de manga", + metadataBase: new URL("https://worstscan.xyz"), + openGraph: { + type: "website", + siteName: "worst-scan", + title: "worst-scan fansub", + description: "Escaneos y traducciones de manga", + url: "https://worstscan.xyz", + }, + twitter: { + card: "summary_large_image", + }, + icons: { + icon: "https://worstscan.xyz/api/cover/306127", + }, } export default function RootLayout({ @@ -11,9 +28,20 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode }>) { return ( + + {/* View Transitions: navegación fluida entre páginas (Chrome/Edge/Safari). + Degrada a navegación normal en navegadores sin soporte. */} +