Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "Next.js Dev (Turbopack)",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"port": 3000
},
{
"name": "Cloudflare Workers Preview",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "preview"],
"port": 8787
}
]
}
35 changes: 14 additions & 21 deletions app/[lang]/(main)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx";
import { blog } from "@/lib/source";
import { File, Files, Folder } from "fumadocs-ui/components/files";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import { Language, uiDictionary, localizeUrl, i18n } from "@/lib/i18n";
import { Language, localizeUrl, i18n } from "@/lib/i18n";
import { displayDate } from "@/lib/date";
import { createBlogMetadata, baseUrl } from "@/lib/metadata";
import { ArticleJsonLd } from "@/components/JsonLd";
Expand All @@ -32,7 +32,6 @@ export default async function Page(props: {
}) {
const params = await props.params;
const page = blog.getPage([params.slug], params.lang);
const t = uiDictionary[params.lang].blog;

if (!page) notFound();

Expand All @@ -46,7 +45,7 @@ export default async function Page(props: {

return (
<div className="relative isolate px-6 py-24 sm:py-32 lg:px-8">
<div className="mx-auto max-w-7xl">
<div className="mx-auto max-w-3xl">
<Breadcrumb lang={params.lang} />
<BreadcrumbJsonLd lang={params.lang} />
{/* Structured data for the article */}
Expand All @@ -55,18 +54,24 @@ export default async function Page(props: {
description={data.description ?? data.title}
datePublished={new Date(data.date).toISOString()}
dateModified={new Date(data.lastModified ?? data.date).toISOString()}
authorName={data.author}
authorName={data.author ?? ""}
url={canonical}
imageUrl={data.image}
/>
<div className="container px-0">
<h2 className="text-4xl font-semibold tracking-tight text-pretty opacity-90 sm:text-5xl">
<div>
<time
dateTime={new Date(data.date).toISOString()}
className="text-sm opacity-60"
>
{displayDate(new Date(data.date), params.lang)}
</time>
<h2 className="mt-2 text-4xl font-semibold tracking-tight text-pretty opacity-90 sm:text-5xl">
{data.title}
</h2>
<p className="mt-2 text-lg/8 opacity-60">{data.description}</p>
</div>
<article className="container flex flex-col px-0 py-8 lg:flex-row">
<div className="prose min-w-0 flex-1 lg:pr-8">
<article className="py-8">
<div className="prose">
<InlineTOC className="mb-8" items={toc} />
<Mdx
components={{
Expand All @@ -80,18 +85,6 @@ export default async function Page(props: {
}}
/>
</div>
<div className="flex flex-col gap-4 border-l p-4 text-sm lg:w-[250px]">
<div>
<p className="mb-1 text-fd-muted-foreground">{t.writtenBy}</p>
<p className="font-medium">{data.author}</p>
</div>
<div>
<p className="mb-1 text-sm text-fd-muted-foreground">{t.at}</p>
<p className="font-medium">
{displayDate(new Date(data.date), params.lang)}
</p>
</div>
</div>
</article>
</div>
</div>
Expand Down Expand Up @@ -133,7 +126,7 @@ export async function generateMetadata(props: {
description: data.description ?? data.title,
publishedTime,
modifiedTime,
authors: [data.author],
authors: data.author ? [data.author] : [],
tags,
image: ogImageUrl,
url: canonical,
Expand Down
2 changes: 1 addition & 1 deletion app/[lang]/(main)/blog/atom.xml/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function GET(
<id>${link}</id>
<updated>${updated}</updated>
<summary>${escape(data.description ?? data.title)}</summary>
<author><name>${escape(data.author)}</name></author>
${data.author ? `<author><name>${escape(data.author)}</name></author>` : ""}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Atom author metadata for authorless posts

This change makes each entry’s <author> conditional, but the feed template still has no feed-level <author> fallback. After author became optional, any post without author will produce an Atom entry with no author metadata, which violates Atom’s required author semantics (author must exist on the entry or be inherited from the feed) and can cause feed readers/validators to reject or drop those entries. Add a feed-level author element or emit a default entry author when data.author is missing.

Useful? React with 👍 / 👎.

</entry>`;
})
.join("");
Expand Down
16 changes: 0 additions & 16 deletions app/[lang]/(main)/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,6 @@ export default async function Page({
{post.data.description}
</p>
</div>
<div className="relative mt-8 flex items-center gap-x-4">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt={`${post.data.author} avatar`}
src={post.data.authorAvatarLink}
className="size-10 rounded-full bg-neutral-50"
/>
<div className="text-sm/6">
<p className="font-semibold opacity-90">
<a href={post.data.authorLink}>
<span className="absolute inset-0" />
{post.data.author}
</a>
</p>
</div>
</div>
</article>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/[lang]/(main)/blog/rss.xml/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function GET(
<guid isPermaLink="true">${link}</guid>
<pubDate>${pub}</pubDate>
<description>${escape(data.description ?? data.title)}</description>
<author>${escape(data.author)}</author>
${data.author ? `<author>${escape(data.author)}</author>` : ""}
</item>`;
})
.join("");
Expand Down
6 changes: 3 additions & 3 deletions source.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export const blog = defineCollections({
type: "doc",
dir: "content/blog",
schema: frontmatterSchema.extend({
author: z.string(),
authorAvatarLink: z.string().url().or(z.string()),
authorLink: z.string().url(),
author: z.string().optional(),
authorAvatarLink: z.string().url().or(z.string()).optional(),
authorLink: z.string().url().optional(),
date: z.date(),
// Optional SEO fields
image: z.string().url().optional(),
Expand Down
Loading