From 4301e50ab36f6c62e43a9b9f44167ef74c5d24f8 Mon Sep 17 00:00:00 2001 From: HangerThem Date: Mon, 19 Oct 2026 14:15:57 +0200 Subject: [PATCH] fix: tag/category routes, recently updated list, feed and no-js flash - Slugify tag/category static params so pre-rendered pages match links; disable dynamic params and return a real 404 via notFound() - Drop bogus generateStaticParams from the tags/categories index pages - Add a styled not-found page - Sort 'Recently updated' by last modification before slicing - Atom feed: correct rel=self URL, use updated dates, valid content type - Swap no-js -> js class in an inline head script before first paint - Markdown links: only route internal paths through next/link and tolerate missing hrefs --- .../categories/[categorySlug]/page.tsx | 16 ++++++------ app/(withRecent)/categories/page.tsx | 7 ------ app/(withRecent)/tags/[tagSlug]/page.tsx | 16 ++++++------ app/(withRecent)/tags/page.tsx | 7 ------ app/feed.xml/route.ts | 14 ++++++++--- app/layout.tsx | 20 +++++++++------ app/not-found.tsx | 16 ++++++++++++ components/post/RecentlyUpdatedPosts.tsx | 25 +++++++++---------- lib/posts.tsx | 16 +++++++----- 9 files changed, 75 insertions(+), 62 deletions(-) create mode 100644 app/not-found.tsx diff --git a/app/(withRecent)/categories/[categorySlug]/page.tsx b/app/(withRecent)/categories/[categorySlug]/page.tsx index 3bbaa97..7eec203 100644 --- a/app/(withRecent)/categories/[categorySlug]/page.tsx +++ b/app/(withRecent)/categories/[categorySlug]/page.tsx @@ -3,6 +3,8 @@ import { getAllCategories, getCategoryBySlug, getPostsByCategory } from '@/lib/c import { dateFormatter } from '@/utils/time' import type { Metadata } from 'next' import Link from 'next/link' +import { notFound } from 'next/navigation' +import { slugify } from '@/utils/slug' type CategoryPageProps = { params: Promise<{ @@ -13,13 +15,16 @@ type CategoryPageProps = { export async function generateStaticParams() { const categories = getAllCategories() return Object.keys(categories).map((category) => ({ - categorySlug: category, + categorySlug: slugify(category), })) } +export const dynamicParams = false + export async function generateMetadata({ params }: CategoryPageProps): Promise { const { categorySlug } = await params const category = getCategoryBySlug(categorySlug) + if (!category) notFound() return { title: `${category} | ${metadata.title!.toString()}`, @@ -39,14 +44,7 @@ export default async function CategoryPage({ params }: CategoryPageProps) { const posts = getPostsByCategory(categorySlug) const category = getCategoryBySlug(categorySlug) - if (!category) { - return ( -
-

Category not found

- Back to categories -
- ) - } + if (!category) notFound() return (
diff --git a/app/(withRecent)/categories/page.tsx b/app/(withRecent)/categories/page.tsx index cd9d8ba..45bc592 100644 --- a/app/(withRecent)/categories/page.tsx +++ b/app/(withRecent)/categories/page.tsx @@ -6,13 +6,6 @@ import Link from 'next/link' import { metadata } from '@/app/metadata' import { dateFormatter } from '@/utils/time' -export async function generateStaticParams() { - const categories = getAllCategories() - return Object.keys(categories).map((category) => ({ - categorySlug: category, - })) -} - export async function generateMetadata(): Promise { return { title: `Categories | ${metadata.title!.toString()}`, diff --git a/app/(withRecent)/tags/[tagSlug]/page.tsx b/app/(withRecent)/tags/[tagSlug]/page.tsx index aacdd63..83588d2 100644 --- a/app/(withRecent)/tags/[tagSlug]/page.tsx +++ b/app/(withRecent)/tags/[tagSlug]/page.tsx @@ -3,6 +3,8 @@ import { getAllTags, getPostsByTag, getTagBySlug } from '@/lib/tags' import { dateFormatter } from '@/utils/time' import type { Metadata } from 'next' import Link from 'next/link' +import { notFound } from 'next/navigation' +import { slugify } from '@/utils/slug' type TagPageProps = { params: Promise<{ @@ -13,13 +15,16 @@ type TagPageProps = { export async function generateStaticParams() { const tags = getAllTags() return Object.keys(tags).map((tag) => ({ - tagSlug: tag, + tagSlug: slugify(tag), })) } +export const dynamicParams = false + export async function generateMetadata({ params }: TagPageProps): Promise { const { tagSlug } = await params const tag = getTagBySlug(tagSlug) + if (!tag) notFound() return { title: `${tag} | ${metadata.title!.toString()}`, @@ -39,14 +44,7 @@ export default async function TagPage({ params }: TagPageProps) { const posts = getPostsByTag(tagSlug) const tag = getTagBySlug(tagSlug) - if (!tag) { - return ( -
-

Tag not found

- Back to tags -
- ) - } + if (!tag) notFound() return (
diff --git a/app/(withRecent)/tags/page.tsx b/app/(withRecent)/tags/page.tsx index 207f829..b023a7e 100644 --- a/app/(withRecent)/tags/page.tsx +++ b/app/(withRecent)/tags/page.tsx @@ -6,13 +6,6 @@ import Link from 'next/link' import { metadata } from '@/app/metadata' import { dateFormatter } from '@/utils/time' -export async function generateStaticParams() { - const tags = getAllTags() - return Object.keys(tags).map((tag) => ({ - tagSlug: tag, - })) -} - export async function generateMetadata(): Promise { return { title: `Tags | ${metadata.title!.toString()}`, diff --git a/app/feed.xml/route.ts b/app/feed.xml/route.ts index e797839..1c027c1 100644 --- a/app/feed.xml/route.ts +++ b/app/feed.xml/route.ts @@ -1,10 +1,13 @@ import { metadata } from '@/app/metadata' import { getAllPostsMeta } from '@/lib/posts' +import type { PostMeta } from '@/types/Post.type' export const dynamic = 'force-static' const SITE_URL = process.env.SITE_URL +const lastModified = (p: PostMeta) => new Date(p.updated ?? p.date) + function escapeXml(str: string) { return str .replace(/&/g, '&') @@ -18,20 +21,23 @@ export async function GET(req: Request) { const posts = getAllPostsMeta() const updated = - posts.length > 0 ? new Date(posts[0].date).toISOString() : new Date().toISOString() + posts.length > 0 + ? new Date(Math.max(...posts.map((p) => lastModified(p).getTime()))).toISOString() + : new Date().toISOString() const entries = posts .map((p) => { const url = `${SITE_URL}/posts/${p.slug}` const date = new Date(p.date).toISOString() + const modified = lastModified(p).toISOString() return ` ${escapeXml(p.title)} ${url} ${date} - ${date} - + ${modified} + ${escapeXml(p.description)} ` }) @@ -45,7 +51,7 @@ export async function GET(req: Request) { ${escapeXml(metadata.creator!.toString())} - + © ${new Date().getFullYear()} Frank Borisjuk ${entries} diff --git a/app/layout.tsx b/app/layout.tsx index e05d642..32fc18a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,4 @@ import { Space_Grotesk } from 'next/font/google' -import Script from 'next/script' import { Sidebar } from '@/components/layout/Sidebar' import { MobileNavbar } from '@/components/layout/MobileNavbar' import { metadata } from '@/app/metadata' @@ -18,12 +17,19 @@ export default function RootLayout({ children: React.ReactNode }>) { return ( - -