From f58766d47ea08aa19b387ab14dd7dfa8d486bd6b Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 13 Jul 2026 17:18:08 -0700 Subject: [PATCH 1/5] fix(landing): align author-page UI with the site shell and link bylines to it Rebuilds ContentAuthorPage/ContentAuthorLoading to match the standard page-shell pattern used by ContentIndexPage/ContentPostPage (max-w-[1460px] header, BackLink, framed post-list rows with hover states) instead of the previous bare max-w-[900px] container with a 2-column image grid. Adds a styled not-found fallback matching the comparisons/integrations pattern. The byline author Link in ContentPostPage now points at the internal author page (/{basePath}/authors/{id}) instead of the author's external X/GitHub profile, giving the author page a real navigation entry point for the first time - previously it was only reachable via the sitemap. Also fixes both author-page route files to match on any of a post's authors (post.authors.some(a => a.id === id)) instead of only the primary author (post.author.id === id), so posts with co-authors show up on every listed author's page, consistent with the sitemap's own author enumeration. --- .../app/(landing)/blog/authors/[id]/page.tsx | 10 +- .../content-author-loading.tsx | 32 ++-- .../content-author-page.tsx | 144 +++++++++++++----- .../content-post-page/content-post-page.tsx | 5 +- .../(landing)/library/authors/[id]/page.tsx | 10 +- 5 files changed, 139 insertions(+), 62 deletions(-) diff --git a/apps/sim/app/(landing)/blog/authors/[id]/page.tsx b/apps/sim/app/(landing)/blog/authors/[id]/page.tsx index 302b843cae6..6d2869eeca5 100644 --- a/apps/sim/app/(landing)/blog/authors/[id]/page.tsx +++ b/apps/sim/app/(landing)/blog/authors/[id]/page.tsx @@ -11,18 +11,20 @@ export async function generateMetadata({ params: Promise<{ id: string }> }): Promise { const { id } = await params - const posts = (await getAllPostMeta()).filter((p) => p.author.id === id) - return buildAuthorMetadata(id, posts[0]?.author) + const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) + const author = posts[0]?.authors.find((a) => a.id === id) + return buildAuthorMetadata(id, author) } export default async function AuthorPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params - const posts = (await getAllPostMeta()).filter((p) => p.author.id === id) - const author = posts[0]?.author + const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) + const author = posts[0]?.authors.find((a) => a.id === id) return ( -
- - +
+
+ +
+ + +
-
+ +
+
+ {Array.from({ length: AUTHOR_POST_SKELETON_COUNT }).map((_, i) => ( -
- -
- - +
+
+ +
+ + +
+
+
))}
- +
) } diff --git a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx index e57ad9e3c64..5c4fdc0ef65 100644 --- a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx +++ b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx @@ -1,11 +1,16 @@ +import { ChipLink } from '@sim/emcn' import Image from 'next/image' import Link from 'next/link' import type { ContentMeta } from '@/lib/content/schema' +import { BackLink } from '@/app/(landing)/components/back-link' +import { Cta } from '@/app/(landing)/components/cta/cta' import { JsonLd } from '@/app/(landing)/components/json-ld' interface ContentAuthorPageProps { /** Route base path, e.g. `/blog` or `/library`. */ basePath: string + /** Section label used in the not-found fallback, e.g. "Blog" or "Library". */ + sectionName: string authorName?: string authorAvatarUrl?: string /** Posts already filtered down to this author. */ @@ -13,9 +18,15 @@ interface ContentAuthorPageProps { graphJsonLd?: Record } -/** Shared author-profile layout for a content section. */ +/** + * Shared author-profile layout for a content section: standard page-shell + * header (matching `ContentIndexPage`/`ContentPostPage`) with avatar + name, + * followed by the author's posts in the same framed-list card style used + * everywhere else on the site. + */ export function ContentAuthorPage({ basePath, + sectionName, authorName, authorAvatarUrl, posts, @@ -23,54 +34,107 @@ export function ContentAuthorPage({ }: ContentAuthorPageProps) { if (!authorName) { return ( -
-

Author not found

+
+

+ Author not found +

+

+ The author you're looking for doesn't exist or has been moved. +

+ + Browse {sectionName} +
) } return ( -
- {graphJsonLd && } -
- {authorAvatarUrl ? ( - {authorName} - ) : null} -

{authorName}

-
-
- {posts.map((p) => ( - -
+ <> +
+ {graphJsonLd && } + +
+
+ +
+ +
+ {authorAvatarUrl ? ( {p.title} -
-
- {new Date(p.date).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric', - })} -
-
{p.title}
+ ) : null} +

+ {authorName} +

+
+
+ +
+ +
+
+ {posts.map((p) => ( +
+ + + {new Date(p.date).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + + +
+ + {new Date(p.date).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + +

+ {p.title} +

+

+ {p.description} +

+
+ +
+ {p.title} +
+ +
-
- - ))} + ))} +
+
+ +
+
+ +
+
-
+ ) } diff --git a/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx b/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx index e7ab328e016..aeb9ca9eb97 100644 --- a/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx +++ b/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx @@ -96,9 +96,8 @@ export function ContentPostPage({ ) : null} }): Promise { const { id } = await params - const posts = (await getAllPostMeta()).filter((p) => p.author.id === id) - return buildAuthorMetadata(id, posts[0]?.author) + const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) + const author = posts[0]?.authors.find((a) => a.id === id) + return buildAuthorMetadata(id, author) } export default async function AuthorPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params - const posts = (await getAllPostMeta()).filter((p) => p.author.id === id) - const author = posts[0]?.author + const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) + const author = posts[0]?.authors.find((a) => a.id === id) return ( Date: Mon, 13 Jul 2026 17:26:03 -0700 Subject: [PATCH 2/5] fix(landing): address review findings on author-page UI rebuild - Not-found fallback no longer renders a nested
- blog/layout.tsx and library/layout.tsx already provide that landmark, so the fallback now uses
like the rest of the component. - Byline author link now encodeURIComponent()s the author id before using it as a route segment. - Loading skeleton's post-list wrapper now matches the real page's mx-auto max-w-[1460px] shell (was mx-20/max-lg:mx-8, which stretches past the header on wide viewports). --- .../content-author-loading.tsx | 28 ++++++++++--------- .../content-author-page.tsx | 7 ++--- .../content-post-page/content-post-page.tsx | 2 +- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/sim/app/(landing)/components/content-author-page/content-author-loading.tsx b/apps/sim/app/(landing)/components/content-author-page/content-author-loading.tsx index 06b1b787c9e..8ba0d5e00eb 100644 --- a/apps/sim/app/(landing)/components/content-author-page/content-author-loading.tsx +++ b/apps/sim/app/(landing)/components/content-author-page/content-author-loading.tsx @@ -14,22 +14,24 @@ export function ContentAuthorLoading() {
-
-
+
- {Array.from({ length: AUTHOR_POST_SKELETON_COUNT }).map((_, i) => ( -
-
- -
- - +
+
+ {Array.from({ length: AUTHOR_POST_SKELETON_COUNT }).map((_, i) => ( +
+
+ +
+ + +
+
- +
-
-
- ))} + ))} +
) diff --git a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx index 5c4fdc0ef65..9d33eef10d3 100644 --- a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx +++ b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx @@ -34,10 +34,7 @@ export function ContentAuthorPage({ }: ContentAuthorPageProps) { if (!authorName) { return ( -
+

Author not found

@@ -47,7 +44,7 @@ export function ContentAuthorPage({ Browse {sectionName} -
+ ) } diff --git a/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx b/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx index aeb9ca9eb97..fccff3158de 100644 --- a/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx +++ b/apps/sim/app/(landing)/components/content-post-page/content-post-page.tsx @@ -96,7 +96,7 @@ export function ContentPostPage({ ) : null} Date: Mon, 13 Jul 2026 17:33:31 -0700 Subject: [PATCH 3/5] fix(landing): make author Person.image an absolute URL in JSON-LD Same class of bug as the earlier article-image fix: author.avatarUrl is a site-relative path, so Person.image was emitting an invalid relative URL for crawlers. Prefixes with SITE_URL, matching buildArticleJsonLd's existing pattern. --- apps/sim/lib/content/seo.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/content/seo.ts b/apps/sim/lib/content/seo.ts index df224f91963..7f421ec490d 100644 --- a/apps/sim/lib/content/seo.ts +++ b/apps/sim/lib/content/seo.ts @@ -320,7 +320,9 @@ export function buildAuthorGraphJsonLd(section: ContentSection, author: Author) name: author.name, url: `${SITE_URL}${section.basePath}/authors/${author.id}`, sameAs: author.url ? [author.url] : [], - image: author.avatarUrl, + image: author.avatarUrl?.startsWith('http') + ? author.avatarUrl + : author.avatarUrl && `${SITE_URL}${author.avatarUrl}`, worksFor: { '@type': 'Organization', name: 'Sim', From 760a77f0bffaeae9030c981c06651ae06f592bd0 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 13 Jul 2026 17:39:42 -0700 Subject: [PATCH 4/5] fix(landing): align author not-found fallback width with the site convention max-w-[1446px]/px-12 was copied from comparisons/not-found.tsx, which turns out to be the outlier - models/not-found.tsx and integrations/not-found.tsx both use max-w-[1460px]/px-20, matching the rest of the site's shell and this component's own success-state header. --- .../components/content-author-page/content-author-page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx index 9d33eef10d3..43225eb95ca 100644 --- a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx +++ b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx @@ -34,7 +34,7 @@ export function ContentAuthorPage({ }: ContentAuthorPageProps) { if (!authorName) { return ( -
+

Author not found

From b593e88c8313e80610b599ff49b4d8478418a4dd Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 13 Jul 2026 17:58:55 -0700 Subject: [PATCH 5/5] fix(landing): encode author id consistently in every author URL builder The byline link encoded the author id, but buildAuthorMetadata's canonical URL, buildAuthorGraphJsonLd's Person.url/BreadcrumbList item, and sitemap.ts's buildAuthorPages URL still interpolated the raw id - a mismatch could point sitemap/canonical/structured-data URLs at a different route than the actual page. All four now encodeURIComponent() consistently. --- apps/sim/app/sitemap.ts | 2 +- apps/sim/lib/content/seo.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/sitemap.ts b/apps/sim/app/sitemap.ts index 4d9ad8d77ea..1bd7a26efa5 100644 --- a/apps/sim/app/sitemap.ts +++ b/apps/sim/app/sitemap.ts @@ -25,7 +25,7 @@ function buildAuthorPages(posts: ContentMeta[], basePath: string): MetadataRoute } } return [...authorsMap.entries()].map(([id, date]) => ({ - url: `${SITE_URL}${basePath}/authors/${id}`, + url: `${SITE_URL}${basePath}/authors/${encodeURIComponent(id)}`, lastModified: date, })) } diff --git a/apps/sim/lib/content/seo.ts b/apps/sim/lib/content/seo.ts index 7f421ec490d..dcad1a7adf0 100644 --- a/apps/sim/lib/content/seo.ts +++ b/apps/sim/lib/content/seo.ts @@ -285,7 +285,7 @@ export function buildAuthorMetadata( author?: Author ): Metadata { const name = author?.name ?? 'Author' - const canonical = `${SITE_URL}${section.basePath}/authors/${id}` + const canonical = `${SITE_URL}${section.basePath}/authors/${encodeURIComponent(id)}` const description = `Read articles by ${name} on the Sim ${section.name.toLowerCase()}.` return { title: `${name} | Sim ${section.name}`, @@ -318,7 +318,7 @@ export function buildAuthorGraphJsonLd(section: ContentSection, author: Author) { '@type': 'Person', name: author.name, - url: `${SITE_URL}${section.basePath}/authors/${author.id}`, + url: `${SITE_URL}${section.basePath}/authors/${encodeURIComponent(author.id)}`, sameAs: author.url ? [author.url] : [], image: author.avatarUrl?.startsWith('http') ? author.avatarUrl @@ -343,7 +343,7 @@ export function buildAuthorGraphJsonLd(section: ContentSection, author: Author) '@type': 'ListItem', position: 3, name: author.name, - item: `${SITE_URL}${section.basePath}/authors/${author.id}`, + item: `${SITE_URL}${section.basePath}/authors/${encodeURIComponent(author.id)}`, }, ], },