Skip to content

Instantly share code, notes, and snippets.

@libetl
Created Aug 31, 2021
Embed
What would you like to do?
dynamic site sections in next.js
import * as React from "react";
import { promises as fs } from "fs";
import { resolve } from "path";
import matter from "gray-matter";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
type PageProps = { documentProps: {
menu: Record<string, string>[];
frontMatter: {
id: string;
linkName: string;
name: string;
author: string | null;
date: string | null;
part: string;
section: string;
};
content: { compiledSource: string; scope?: Record<string, unknown> };
} }
const titleToId = (title: string) =>
title
.toLowerCase()
.replace(/\s+/, "-")
.replace(/[^A-Za-z0-9-]/g, "");
const components = {
img: (props) => <img {...props} />,
h1: ({ children, ...props }) => (
<>
<a id={titleToId(children.toString())}></a>
<h1 {...props}>
{children}
</h1>
</>
),
h2: ({ children, ...props }) => (
<>
<a id={titleToId(children.toString())}></a>
<p style={{marginTop: "6em", marginBottom: "2em"}}>
<h2 {...props}>
{children}
</h2>
</p>
</>
),
h3: ({ children, ...props }) => (
<>
<a id={titleToId(children.toString())}></a>
<h3 {...props}>
{children}
</h3>
</>
),
h4: ({ children, ...props }) => (
<>
<a id={titleToId(children.toString())}></a>
<h4 {...props}>
{children}
</h4>
</>
),
h5: ({ children, ...props }) => (
<>
<a id={titleToId(children.toString())}></a>
<h5 {...props}>
{children}
</h5>
</>
),
h6: ({ children, ...props }) => (
<>
<a id={titleToId(children.toString())}></a>
<h6 {...props}>
{children}
</h6>
</>
),
hr: ({ children, ...props }) => <hgroup />,
p: ({ children, ...props }) => (
<p {...props}>{children}</p>
),
ol: ({ children, ...props }) => (
<ol {...props}>
{children}
</ol>
),
ul: ({ children, ...props }) => <ul {...props}>{children}</ul>,
li: ({ children, ...props }) => (
<li {...props}>{children}</li>
),
code: ({ children, ...props }) => (
<code {...props} className="docs-pre">
{children}
</code>
),
inlineCode: ({ children, ...props }) => <code>{children}</code>,
};
class OnePage extends React.Component<PageProps> {
public render() {
return (
<p style={{marginTop: "6em", marginBottom: "2em"}}>
<section id={this.props.documentProps.frontMatter.id}>
<p style={{margin: "2em"}}>
<h1>
{this.props.documentProps.frontMatter.name}
</h1>
</p>
<table>
<tbody>
{[
["date", this.props.documentProps.frontMatter.date],
["author", this.props.documentProps.frontMatter.author],
["name", this.props.documentProps.frontMatter.name],
["section", this.props.documentProps.frontMatter.section],
]
.filter(([key, value]) => value)
.map(([key, value]) => (
<tr key={key}>
<td>
<b>{key}</b>
</td>
<td>{value}</td>
</tr>
))}
</tbody>
</table>
<MDXRemote
{...this.props.documentProps.content}
components={components}
scope={this.props.documentProps}
/>
</section>
</p>
);
}
}
export async function getStaticProps({
params: { page },
}): Promise<{props: PageProps}> {
const allFrontMatters = await readAllFrontMatters();
const currentPage = allFrontMatters.find(
(metadata) => metadata[2].data.linkName === page || metadata[1] === page
);
const currentPart = currentPage[2].data.part ?? currentPage[0];
const menu = groupBySection(allFrontMatters)[currentPart];
return {
props: {
documentProps: {
menu,
frontMatter: {
id: currentPage[2].data.id ?? currentPage[1],
linkName: currentPage[2].data.linkName ?? currentPage[1],
name: currentPage[2].data.name ?? currentPage[1],
author: currentPage[2].data.author ?? null,
date: currentPage[2].data.date ?? null,
part: currentPart,
section: currentPage[2].data.section ?? "all",
},
content: await serialize(currentPage[2].content),
},
},
};
}
export async function getStaticPaths() {
const allFrontMatters = await readAllFrontMatters();
return {
paths: allFrontMatters.map((frontmatter) => {
return {
params: {
page: frontmatter[2].data.linkName || frontmatter[1],
part: frontmatter[2].data.part || frontmatter[0],
},
};
}),
fallback: false,
};
}
function groupBySection(
metadata: [string, string, matter.GrayMatterFile<string>][]
): Record<string, Record<string, string>[]> {
const parts = metadata.map((array) => array[0]);
return Object.assign(
{},
...parts.map((part) => ({
[part]: metadata
.filter((array) => array[0] === part)
.map((page) => ({
linkName: page[2].data.linkName ?? page[1],
name: page[2].data.name ?? page[1],
section: page[2].data.section ?? "all",
part: page[2].data.part ?? page[0],
data: page[2].data,
}))
.reduce((acc, value) => {
const section = value.section ?? "all";
acc[section] = (acc[section] ?? []).concat(value);
return acc;
}, {}),
}))
);
}
async function readAllFrontMatters(): Promise<
[string, string, matter.GrayMatterFile<string>][]
> {
return ((
await Promise.all(
["wiki", "blog"].flatMap((dir) =>
fs
.readdir(resolve(process.cwd(), dir))
.then((list) =>
Promise.all(
list.map((fileName) =>
fs
.readFile(resolve(process.cwd(), dir, fileName))
.then((content) => [dir, fileName, content.toString()])
)
)
)
)
)
).flatMap((a) => a) as [string, string, string][]).map(
([part, fileName, buffer]) => {
return [part, fileName.replace(/\.mdx?$/, ""), matter(buffer.toString())];
}
);
}
export default OnePage;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment