Skip to content

Commit 71b2927

Browse files
frano-mCopilot
andauthored
feat!: add get static props helper function (#691) (#692)
* feat: add get static props helper function (#691) * refactor: file import (#691) * Update src/utils/mdx/frontmatter/validateMatter.ts Co-authored-by: Copilot <[email protected]> * Update src/utils/mdx/frontmatter/validateMatter.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Fran McDade <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent b130b3c commit 71b2927

File tree

8 files changed

+1483
-34
lines changed

8 files changed

+1483
-34
lines changed

package-lock.json

Lines changed: 1331 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@
7676
"@tanstack/react-table": "^8.19.2",
7777
"@tanstack/react-virtual": "^3.13.12",
7878
"copy-to-clipboard": "3.3.1",
79+
"gray-matter": "^4.0.3",
7980
"isomorphic-dompurify": "^2.22.0",
8081
"ky": "^1.7.2",
8182
"next": "^14.2.32",
8283
"next-auth": "4.24.11",
84+
"next-mdx-remote": "^4.4.1",
8385
"react": "^18.3.1",
8486
"react-dom": "^18.3.1",
8587
"react-dropzone": "^14.2.3",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import fs from "fs";
2+
import matter from "gray-matter";
3+
4+
/**
5+
* Returns matter object (frontmatter and content) from the given MD/MDX path.
6+
* Server-only: uses Node fs, import only in SSG/SSR/build contexts.
7+
* @param filePath - File path of MD/MDX file.
8+
* @returns matter object.
9+
*/
10+
export function getMatter(filePath: string): matter.GrayMatterFile<string> {
11+
const markdownWithMeta = fs.readFileSync(filePath, "utf-8");
12+
return matter(markdownWithMeta);
13+
}

src/utils/mdx/frontmatter/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface BaseFrontmatterProps {
2+
description: string;
3+
hidden?: boolean;
4+
title: string;
5+
}
6+
7+
export type FrontmatterProps<F extends object> = BaseFrontmatterProps & F;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import matter from "gray-matter";
2+
import { FrontmatterProps } from "./types";
3+
4+
/**
5+
* Returns the frontmatter from the given gray matter file data.
6+
* @param data - Gray matter file data.
7+
* @returns Frontmatter.
8+
*/
9+
export function validateMatter<F extends object>(
10+
data: matter.GrayMatterFile<string>["data"]
11+
): FrontmatterProps<F> | undefined {
12+
if (
13+
"title" in data &&
14+
typeof data.title === "string" &&
15+
"description" in data &&
16+
typeof data.description === "string"
17+
) {
18+
return data as FrontmatterProps<F>;
19+
}
20+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { SerializeOptions } from "next-mdx-remote/dist/types";
2+
import { serialize } from "next-mdx-remote/serialize";
3+
import { GetStaticPropsResult } from "next/types";
4+
import remarkGfm from "remark-gfm";
5+
import { OutlineItem } from "../../../components/Layout/components/Outline/types";
6+
import { getMatter } from "../frontmatter/getMatter";
7+
import { FrontmatterProps } from "../frontmatter/types";
8+
import { validateMatter } from "../frontmatter/validateMatter";
9+
import { rehypeSlug } from "../plugins/rehypeSlug";
10+
import { remarkHeadings } from "../plugins/remarkHeadings";
11+
import { StaticProps } from "./types";
12+
13+
export async function buildStaticProps<F extends object, P extends object>(
14+
fileName: string | undefined,
15+
slug: string[] | undefined,
16+
frontmatterFn = (
17+
frontmatter: FrontmatterProps<F> | undefined
18+
): FrontmatterProps<F> | undefined => frontmatter,
19+
serializeOptions: SerializeOptions = {},
20+
otherProps: P = {} as P
21+
): Promise<
22+
GetStaticPropsResult<StaticProps<FrontmatterProps<F>, P>> | undefined
23+
> {
24+
if (!slug) return;
25+
if (!fileName) return;
26+
27+
// Extract frontmatter and content from the MDX file.
28+
const { content, data } = getMatter(fileName);
29+
const frontmatter = frontmatterFn(validateMatter(data));
30+
31+
// If the frontmatter is hidden, return.
32+
if (!frontmatter || frontmatter.hidden) return;
33+
34+
// We expect the frontmatter to have a title.
35+
if (!frontmatter.title) return;
36+
37+
// Serialize the MDX content.
38+
const outline: OutlineItem[] = [];
39+
const mdxSource = await serialize(content, {
40+
...serializeOptions,
41+
mdxOptions: {
42+
development: false,
43+
rehypePlugins: [rehypeSlug],
44+
remarkPlugins: [[remarkHeadings, { outline }], remarkGfm],
45+
...serializeOptions.mdxOptions,
46+
},
47+
scope: { ...serializeOptions.scope, frontmatter },
48+
});
49+
50+
const { title: pageTitle } = frontmatter;
51+
52+
return {
53+
props: {
54+
frontmatter,
55+
mdxSource,
56+
outline,
57+
pageTitle,
58+
slug,
59+
...otherProps,
60+
},
61+
};
62+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { MDXRemoteSerializeResult } from "next-mdx-remote";
2+
import { OutlineItem } from "../../../components/Layout/components/Outline/types";
3+
import { FrontmatterProps } from "../frontmatter/types";
4+
5+
interface BaseStaticProps<F extends object> {
6+
frontmatter: FrontmatterProps<F> | null;
7+
mdxSource: MDXRemoteSerializeResult | null;
8+
outline: OutlineItem[] | null;
9+
pageTitle: string;
10+
slug: string[];
11+
}
12+
13+
export type StaticProps<
14+
F extends object,
15+
P extends object
16+
> = BaseStaticProps<F> & P;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { GetStaticPropsContext } from "next/types";
2+
import { resolveRelativeDirs } from "../files/resolveRelativeDirs";
3+
4+
/**
5+
* Builds the MDX file path from the given list of doc directories and slug.
6+
* @param dirs - Doc directories e.g. ["app", "docs"].
7+
* @param slug - Doc slug e.g. ["learn", "featured-analyses"].
8+
* @returns MDX file path.
9+
*/
10+
export function buildMDXFilePath(
11+
dirs: string[],
12+
slug: string[] | undefined
13+
): string | undefined {
14+
if (!slug) return;
15+
return resolveRelativeDirs(dirs.concat(slug)).concat(".mdx");
16+
}
17+
18+
/**
19+
* Returns the MDX page slug for the given static props context and section.
20+
* @param props - Static props context.
21+
* @param section - Docs section e.g. "learn".
22+
* @returns MDX page slug.
23+
*/
24+
export function buildMDXSlug(
25+
props: GetStaticPropsContext,
26+
section?: string
27+
): string[] | undefined {
28+
const slug = props.params?.slug;
29+
if (!slug || typeof slug === "string") return;
30+
if (section) return [section, ...slug];
31+
return slug;
32+
}

0 commit comments

Comments
 (0)