Skip to content

Commit 7bb760e

Browse files
committed
refactor: eject from next-mdx-remote
1 parent 2614034 commit 7bb760e

File tree

10 files changed

+304
-209
lines changed

10 files changed

+304
-209
lines changed

app/posts/[slug]/actions.test.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import * as postsActions from '../actions'
2-
3-
import { fetchPostBySlug, fetchPreviousPostSlug } from './actions'
1+
import { fetchPostBySlug } from './actions'
42

53
import * as mdx from '@/lib/mdx'
64
import type { Post } from '@/lib/types'
75
import { getMockFrontmatter } from '@/test/mocks/frontmatter'
8-
import { getMockSource } from '@/test/mocks/source'
96

107
vi.mock('@/lib/mdx')
118
vi.mock('../actions')
@@ -56,25 +53,4 @@ describe('/posts/[slug]/actions', () => {
5653
})
5754
})
5855
})
59-
60-
describe('fetchPreviousPostBySlug', () => {
61-
const mockPosts: Post[] = [
62-
{ slug: 'slug-1', ...getMockFrontmatter(), source: getMockSource() },
63-
{ slug: 'slug-2', ...getMockFrontmatter(), source: getMockSource() },
64-
]
65-
66-
it('returns the next index of results returned from fetchPublishedPosts()', async () => {
67-
vi.mocked(postsActions.fetchPublishedPosts).mockResolvedValue(mockPosts)
68-
69-
const actual = await fetchPreviousPostSlug('slug-1')
70-
expect(actual).toEqual('slug-2')
71-
})
72-
73-
it('returns undefined when the bottom of the list is reached', async () => {
74-
vi.mocked(postsActions.fetchPublishedPosts).mockResolvedValue(mockPosts)
75-
76-
const actual = await fetchPreviousPostSlug('slug-2')
77-
expect(actual).toBeUndefined()
78-
})
79-
})
8056
})

app/posts/[slug]/actions.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import path from 'path'
44

55
import { notFound } from 'next/navigation'
66

7-
import { fetchPublishedPosts } from '../actions'
8-
97
import { getPostFromMDX } from '@/lib/mdx'
108
import type { Post } from '@/lib/types'
119

@@ -22,14 +20,3 @@ export async function fetchPostBySlug(slug: string): Promise<Post> {
2220
}
2321
}
2422
}
25-
26-
export async function fetchPreviousPostSlug(slug: string): Promise<string | undefined> {
27-
const publishedPosts = await fetchPublishedPosts()
28-
const postIndex = publishedPosts.findIndex((post) => post.slug === slug)
29-
30-
if (postIndex === publishedPosts.length - 1) return undefined
31-
32-
const previousPost = publishedPosts.at(postIndex + 1)
33-
34-
return previousPost && previousPost.slug
35-
}

app/posts/[slug]/mdx-content.tsx

Lines changed: 0 additions & 68 deletions
This file was deleted.

app/posts/[slug]/page.tsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { ChevronLeft, ChevronRight } from 'lucide-react'
2-
import { Metadata } from 'next'
1+
import { ChevronLeft } from 'lucide-react'
2+
import { type Metadata } from 'next'
33
import Image from 'next/image'
44
import Link from 'next/link'
55

66
import { fetchPublishedPosts } from '../actions'
77

8-
import { fetchPostBySlug, fetchPreviousPostSlug } from './actions'
9-
import MDXContent from './mdx-content'
8+
import { fetchPostBySlug } from './actions'
109
import TimeInformation from './time-information'
1110

1211
import { Tag } from '@/components/tag'
@@ -19,8 +18,8 @@ import { cn } from '@/lib/utils'
1918
export async function generateStaticParams() {
2019
const publishedPosts = await fetchPublishedPosts()
2120

22-
return publishedPosts.map((post) => ({
23-
slug: post.slug,
21+
return publishedPosts.map(({ slug }) => ({
22+
slug,
2423
}))
2524
}
2625

@@ -51,7 +50,9 @@ export const generateMetadata = async ({ params }: { params: Promise<{ slug: str
5150

5251
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
5352
const { slug } = await params
54-
const [post, previousPostSlug] = await Promise.all([fetchPostBySlug(slug), fetchPreviousPostSlug(slug)])
53+
const post = await fetchPostBySlug(slug)
54+
55+
const { default: Post } = await import(`@/posts/${slug}.mdx`)
5556

5657
return (
5758
<div className="mx-0 my-10 flex flex-col items-center md:mx-20">
@@ -68,22 +69,13 @@ export default async function PostPage({ params }: { params: Promise<{ slug: str
6869
<TimeInformation metadata={{ publishedAt: post.publishedAt, source: post.source }} />
6970
</div>
7071
<article className="mt-8">
71-
<MDXContent source={post.source} />
72+
<Post />
7273
</article>
7374
<div className="border-color mt-8 flex w-full flex-row justify-center gap-4 border-t pt-8">
7475
<Link href="/posts" className={cn(buttonVariants({ variant: 'outline' }), 'w-2/5 md:w-1/2')}>
7576
<ChevronLeft width={16} height={16} />
7677
&nbsp;All posts
7778
</Link>
78-
{previousPostSlug && (
79-
<Link
80-
href={`/posts/${previousPostSlug}`}
81-
className={cn(buttonVariants({ variant: 'outline' }), 'w-2/5 md:w-1/2')}
82-
>
83-
<ChevronRight width={16} height={16} />
84-
&nbsp;Next
85-
</Link>
86-
)}
8779
</div>
8880
</div>
8981
)

mdx-components.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { MDXComponents } from 'mdx/types'
2+
import Image, { type ImageProps } from 'next/image'
3+
import type { HTMLAttributes, PropsWithChildren } from 'react'
4+
5+
export function useMDXComponents(components: MDXComponents): MDXComponents {
6+
return {
7+
...{
8+
img: (props: HTMLAttributes<HTMLImageElement>) => (
9+
<Image
10+
{...(props as ImageProps)}
11+
alt="article image"
12+
className="my-4 aspect-video rounded-lg object-center"
13+
width={1600}
14+
height={900}
15+
/>
16+
),
17+
h1: (props: PropsWithChildren) => <h1 className="prose my-8 text-4xl font-semibold md:my-12" {...props} />,
18+
h2: (props: PropsWithChildren) => <h2 className="prose my-6 text-3xl font-semibold md:my-8" {...props} />,
19+
h3: (props: PropsWithChildren) => <h3 className="prose my-6 text-2xl font-semibold md:my-8" {...props} />,
20+
p: (props: PropsWithChildren) => <h4 className="text-normal w-prose my-2 font-normal" {...props} />,
21+
ol: (props: PropsWithChildren) => <ol className="my-6 ml-6 list-decimal [&>li]:mt-2" {...props} />,
22+
ul: (props: PropsWithChildren) => <ul className="my-6 ml-6 list-disc [&>li]:mt-2" {...props} />,
23+
blockquote: (props: PropsWithChildren) => <blockquote className="my-6 border-l-2 pl-6 italic" {...props} />,
24+
code: (props: PropsWithChildren) => (
25+
<code
26+
className="bg-muted relative rounded-lg px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
27+
{...props}
28+
/>
29+
),
30+
pre: ({ className, ...rest }: HTMLAttributes<HTMLElement>) => (
31+
<pre className="my-5 whitespace-pre-wrap [&>code:nth-child(1)]:p-3" {...rest} />
32+
),
33+
a: (props: PropsWithChildren) => (
34+
<a className="text-md text-primary/75 p-0 underline underline-offset-2" {...props} />
35+
),
36+
},
37+
...components,
38+
}
39+
}

next.config.mjs

Lines changed: 0 additions & 24 deletions
This file was deleted.

next.config.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import createMDX from '@next/mdx'
2+
import { type NextConfig } from 'next'
3+
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
4+
import rehypePrettyCode from 'rehype-pretty-code'
5+
import rehypeSlug from 'rehype-slug'
6+
import remarkFrontmatter from 'remark-frontmatter'
7+
import remarkGfm from 'remark-gfm'
8+
import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
9+
import { type HighlighterCoreOptions, getSingletonHighlighter } from 'shiki'
10+
11+
const nextConfig: NextConfig = {
12+
reactStrictMode: true,
13+
images: {
14+
remotePatterns: [],
15+
},
16+
transpilePackages: ['next-mdx-remote'],
17+
pageExtensions: ['md', 'mdx', 'ts', 'tsx'],
18+
async rewrites() {
19+
return [
20+
{
21+
source: '/ingest/static/:path*',
22+
destination: 'https://us-assets.i.posthog.com/static/:path*',
23+
},
24+
{
25+
source: '/ingest/:path*',
26+
destination: 'https://us.i.posthog.com/:path*',
27+
},
28+
]
29+
},
30+
// ? NOTE: This is required to support PostHog trailing slash API requests
31+
skipTrailingSlashRedirect: true,
32+
}
33+
34+
const withMDX = createMDX({
35+
extension: /\.(md|mdx)$/,
36+
options: {
37+
remarkPlugins: [remarkGfm, remarkFrontmatter, remarkMdxFrontmatter],
38+
rehypePlugins: [
39+
rehypeSlug,
40+
rehypeAutolinkHeadings,
41+
[
42+
rehypePrettyCode,
43+
{
44+
theme: 'one-dark-pro',
45+
keepBackground: false,
46+
getHighlighter: (options: HighlighterCoreOptions) => {
47+
return getSingletonHighlighter({
48+
...options,
49+
themes: ['one-dark-pro'],
50+
langs: ['js', 'ts', 'jsx', 'tsx', 'json', 'json5', 'shell', 'bash', 'astro', 'markdown'],
51+
})
52+
},
53+
},
54+
],
55+
],
56+
},
57+
})
58+
59+
export default withMDX(nextConfig)

package.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
}
1414
},
1515
"lint-staged": {
16-
"*.{ts,tsx}": ["next lint --fix", "pnpm format"],
16+
"*.{ts,tsx}": [
17+
"next lint --fix",
18+
"pnpm format"
19+
],
1720
"*.md": "prettier --list-different"
1821
},
1922
"scripts": {
@@ -39,6 +42,9 @@
3942
"typecheck": "tsc --pretty"
4043
},
4144
"dependencies": {
45+
"@mdx-js/loader": "3.1.0",
46+
"@mdx-js/react": "3.1.0",
47+
"@next/mdx": "15.3.5",
4248
"@radix-ui/react-accordion": "1.2.11",
4349
"@radix-ui/react-aspect-ratio": "1.1.7",
4450
"@radix-ui/react-avatar": "1.1.10",
@@ -56,15 +62,13 @@
5662
"geist": "1.4.2",
5763
"lucide-react": "0.511.0",
5864
"next": "15.3.3",
59-
"next-mdx-remote": "5.0.0",
6065
"next-themes": "0.4.6",
6166
"posthog-js": "1.255.0",
6267
"posthog-node": "4.17.2",
6368
"react": "19.1.0",
6469
"react-dom": "19.1.0",
6570
"rss": "1.2.2",
6671
"sharp": "0.34.2",
67-
"shiki": "3.4.2",
6872
"tailwind-merge": "3.3.1"
6973
},
7074
"devDependencies": {
@@ -74,9 +78,11 @@
7478
"@next/eslint-plugin-next": "15.3.4",
7579
"@playwright/test": "1.53.0",
7680
"@tailwindcss/postcss": "4.1.11",
81+
"@tailwindcss/typography": "0.5.16",
7782
"@testing-library/jest-dom": "6.6.3",
7883
"@testing-library/react": "16.3.0",
7984
"@testing-library/user-event": "14.6.1",
85+
"@types/mdx": "2.0.13",
8086
"@types/node": "22.7.5",
8187
"@types/react": "19.1.8",
8288
"@types/react-dom": "19.1.5",
@@ -104,7 +110,10 @@
104110
"rehype-autolink-headings": "7.1.0",
105111
"rehype-pretty-code": "0.14.1",
106112
"rehype-slug": "6.0.0",
113+
"remark-frontmatter": "5.0.0",
107114
"remark-gfm": "4.0.1",
115+
"remark-mdx-frontmatter": "5.2.0",
116+
"shiki": "3.4.2",
108117
"tailwindcss": "4.1.10",
109118
"tailwindcss-animate": "1.0.7",
110119
"tw-animate-css": "1.3.0",

0 commit comments

Comments
 (0)