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
52 changes: 52 additions & 0 deletions apps/blog/app/(en)/essays/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ImageResponse } from 'next/og';
import { getEssayBySlug, getEssaySlugs } from '@/lib/essays';
import { OgCard, OG_WIDTH, OG_HEIGHT } from '@/components/seo/og-card';
import { ogFontsFor } from '@/lib/og-fonts';
import { TOPIC_LABELS } from '@/lib/constants';

export const dynamic = 'force-static';

export const alt = 'Algo Mind essay';
export const size = { width: OG_WIDTH, height: OG_HEIGHT };
export const contentType = 'image/png';

export function generateStaticParams() {
return getEssaySlugs().map((slug) => ({ slug }));
}

interface RouteProps {
params: { slug: string };
}

export default async function OgImage({ params }: RouteProps) {
const fonts = ogFontsFor('en');
const essay = getEssayBySlug(params.slug);
if (!essay || essay.lang !== 'en') {
return new ImageResponse(
(
<OgCard
title="Algo Mind"
byline="Feitong Yang"
brand="Algo Mind"
locale="en"
/>
),
{ ...size, fonts },
);
}

const kicker = essay.topics[0] ? TOPIC_LABELS[essay.topics[0]] : 'Essay';

return new ImageResponse(
(
<OgCard
title={essay.title}
kicker={kicker}
byline="Feitong Yang"
brand="Algo Mind"
locale="en"
/>
),
{ ...size, fonts },
);
}
55 changes: 40 additions & 15 deletions apps/blog/app/(en)/essays/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { getEssayBySlug, getEssaySlugs, getTranslation } from '@/lib/essays';
import { getMDXComponents } from '@/components/mdx/MDXComponents';
import { mdxOptions } from '@/lib/mdx-options';
import { EssayLayout, EssayHeader } from '@/components/essay';
import { JsonLd } from '@/components/seo';
import { breadcrumbSchema, essayPostingSchema } from '@/lib/jsonld';

interface EssayPageProps {
params: Promise<{ slug: string }>;
Expand Down Expand Up @@ -53,6 +55,13 @@ export async function generateMetadata({
alternates.languages!['zh'] = `/zh/essays/${zhTranslation.slug}`;
}

// If the essay declares a hand-picked image, override the autogenerated
// opengraph-image. Next will then use this URL for both og:image and
// twitter:image. Otherwise the file-convention opengraph-image takes over.
const ogOverride = essay.image
? { openGraph: { images: [essay.image] }, twitter: { images: [essay.image] } }
: {};

return {
title: essay.title,
description: essay.description,
Expand All @@ -62,6 +71,11 @@ export async function generateMetadata({
type: 'article',
publishedTime: essay.date,
tags: essay.topics,
...ogOverride.openGraph,
},
twitter: {
card: 'summary_large_image',
...ogOverride.twitter,
},
alternates,
};
Expand Down Expand Up @@ -150,22 +164,33 @@ export default async function EssayPage({ params }: EssayPageProps) {

const { title, description, date, type, topics, readingTime, content, toc } =
essay;
const urlPath = `/essays/${slug}`;

return (
<EssayLayout
toc={toc}
header={
<EssayHeader
type={type}
topics={topics}
title={title}
description={description}
date={date}
readingTime={readingTime}
/>
}
>
<MDXRemote source={content} components={getMDXComponents()} options={{ mdxOptions }} />
</EssayLayout>
<>
<JsonLd data={essayPostingSchema(essay, urlPath)} />
<JsonLd
data={breadcrumbSchema([
{ name: 'Home', url: '/' },
{ name: 'Essays', url: '/essays' },
{ name: title, url: urlPath },
])}
/>
<EssayLayout
toc={toc}
header={
<EssayHeader
type={type}
topics={topics}
title={title}
description={description}
date={date}
readingTime={readingTime}
/>
}
>
<MDXRemote source={content} components={getMDXComponents()} options={{ mdxOptions }} />
</EssayLayout>
</>
);
}
9 changes: 9 additions & 0 deletions apps/blog/app/(en)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from '@/components/layout';
import { LanguageProvider } from '@/lib/i18n';
import { SITE_AUTHOR, SITE_URL } from '@/lib/constants';
import { JsonLd } from '@/components/seo';
import { siteGraph } from '@/lib/jsonld';

export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
Expand Down Expand Up @@ -58,6 +60,13 @@ export default function EnRootLayout({ children }: EnRootLayoutProps) {
return (
<html lang="en" data-theme="nyt" data-mode="light" suppressHydrationWarning>
<head>
<link
rel="alternate"
type="application/atom+xml"
title="Algo Mind — Essays"
href="/feed.xml"
/>
<JsonLd data={siteGraph('en')} />
<script
dangerouslySetInnerHTML={{
__html: `
Expand Down
23 changes: 23 additions & 0 deletions apps/blog/app/(en)/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ImageResponse } from 'next/og';
import { OgCard, OG_WIDTH, OG_HEIGHT } from '@/components/seo/og-card';
import { ogFontsFor } from '@/lib/og-fonts';

export const dynamic = 'force-static';

export const alt = 'Algo Mind — Essays on AI, Product, Engineering';
export const size = { width: OG_WIDTH, height: OG_HEIGHT };
export const contentType = 'image/png';

export default function OgImage() {
return new ImageResponse(
(
<OgCard
title="Essays on AI, Product, Engineering"
byline="Feitong Yang"
brand="Algo Mind"
locale="en"
/>
),
{ ...size, fonts: ogFontsFor('en') },
);
}
45 changes: 45 additions & 0 deletions apps/blog/app/(en)/periodics/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ImageResponse } from 'next/og';
import { getPeriodicBySlug, getPeriodicSlugs } from '@/lib/periodics';
import { OgCard, OG_WIDTH, OG_HEIGHT } from '@/components/seo/og-card';
import { ogFontsFor } from '@/lib/og-fonts';

export const dynamic = 'force-static';

export const alt = 'Algo Mind periodic';
export const size = { width: OG_WIDTH, height: OG_HEIGHT };
export const contentType = 'image/png';

export function generateStaticParams() {
return getPeriodicSlugs().map((slug) => ({ slug }));
}

export default async function OgImage({ params }: { params: { slug: string } }) {
const fonts = ogFontsFor('en');
const periodic = getPeriodicBySlug(params.slug);
if (!periodic || periodic.lang !== 'en') {
return new ImageResponse(
(
<OgCard
title="Algo Mind"
byline="Feitong Yang"
brand="Algo Mind"
locale="en"
/>
),
{ ...size, fonts },
);
}

return new ImageResponse(
(
<OgCard
title={periodic.title}
kicker={`Issue ${periodic.issue}`}
byline="Feitong Yang"
brand="Algo Mind"
locale="en"
/>
),
{ ...size, fonts },
);
}
45 changes: 29 additions & 16 deletions apps/blog/app/(en)/periodics/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getMDXComponents } from '@/components/mdx/MDXComponents';
import { mdxOptions } from '@/lib/mdx-options';
import { EssayLayout } from '@/components/essay';
import { PeriodicHeader } from '@/components/periodic';
import { JsonLd } from '@/components/seo';
import { breadcrumbSchema, periodicPostingSchema } from '@/lib/jsonld';

interface PeriodicPageProps {
params: Promise<{ slug: string }>;
Expand Down Expand Up @@ -152,23 +154,34 @@ export default async function PeriodicPage({ params }: PeriodicPageProps) {

const { title, description, date, issue, type, topics, readingTime, content, toc } =
periodic;
const urlPath = `/periodics/${slug}`;

return (
<EssayLayout
toc={toc}
header={
<PeriodicHeader
issue={issue}
type={type}
topics={topics}
title={title}
description={description}
date={date}
readingTime={readingTime}
/>
}
>
<MDXRemote source={content} components={getMDXComponents()} options={{ mdxOptions }} />
</EssayLayout>
<>
<JsonLd data={periodicPostingSchema(periodic, urlPath)} />
<JsonLd
data={breadcrumbSchema([
{ name: 'Home', url: '/' },
{ name: 'Periodics', url: '/periodics' },
{ name: title, url: urlPath },
])}
/>
<EssayLayout
toc={toc}
header={
<PeriodicHeader
issue={issue}
type={type}
topics={topics}
title={title}
description={description}
date={date}
readingTime={readingTime}
/>
}
>
<MDXRemote source={content} components={getMDXComponents()} options={{ mdxOptions }} />
</EssayLayout>
</>
);
}
46 changes: 46 additions & 0 deletions apps/blog/app/(en)/series/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ImageResponse } from 'next/og';
import { getSeriesBySlug, getSeriesSlugs } from '@/lib/series';
import { OgCard, OG_WIDTH, OG_HEIGHT } from '@/components/seo/og-card';
import { ogFontsFor } from '@/lib/og-fonts';
import { SERIES_CATEGORY_LABELS } from '@/lib/constants';

export const dynamic = 'force-static';

export const alt = 'Algo Mind series';
export const size = { width: OG_WIDTH, height: OG_HEIGHT };
export const contentType = 'image/png';

export function generateStaticParams() {
return getSeriesSlugs().map((slug) => ({ slug }));
}

export default async function OgImage({ params }: { params: { slug: string } }) {
const fonts = ogFontsFor('en');
const series = getSeriesBySlug(params.slug);
if (!series || series.lang !== 'en') {
return new ImageResponse(
(
<OgCard
title="Algo Mind"
byline="Feitong Yang"
brand="Algo Mind"
locale="en"
/>
),
{ ...size, fonts },
);
}

return new ImageResponse(
(
<OgCard
title={series.title}
kicker={SERIES_CATEGORY_LABELS[series.category]}
byline="Feitong Yang"
brand="Algo Mind"
locale="en"
/>
),
{ ...size, fonts },
);
}
47 changes: 30 additions & 17 deletions apps/blog/app/(en)/series/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getMDXComponents } from '@/components/mdx/MDXComponents';
import { mdxOptions } from '@/lib/mdx-options';
import { EssayLayout } from '@/components/essay';
import { SeriesHeader } from '@/components/series';
import { JsonLd } from '@/components/seo';
import { breadcrumbSchema, seriesPageSchema } from '@/lib/jsonld';

interface SeriesPageProps {
params: Promise<{ slug: string }>;
Expand Down Expand Up @@ -150,24 +152,35 @@ export default async function SeriesItemPage({ params }: SeriesPageProps) {

const { title, description, date, updated, category, topics, itemCount, readingTime, content, toc } =
series;
const urlPath = `/series/${slug}`;

return (
<EssayLayout
toc={toc}
header={
<SeriesHeader
category={category}
topics={topics}
title={title}
description={description}
date={date}
updated={updated}
itemCount={itemCount}
readingTime={readingTime}
/>
}
>
<MDXRemote source={content} components={getMDXComponents()} options={{ mdxOptions }} />
</EssayLayout>
<>
<JsonLd data={seriesPageSchema(series, urlPath)} />
<JsonLd
data={breadcrumbSchema([
{ name: 'Home', url: '/' },
{ name: 'Series', url: '/series' },
{ name: title, url: urlPath },
])}
/>
<EssayLayout
toc={toc}
header={
<SeriesHeader
category={category}
topics={topics}
title={title}
description={description}
date={date}
updated={updated}
itemCount={itemCount}
readingTime={readingTime}
/>
}
>
<MDXRemote source={content} components={getMDXComponents()} options={{ mdxOptions }} />
</EssayLayout>
</>
);
}
Loading