1
0
Fork 0

fix(dev): Load MDX dynamically to reduce the number of routes

Prior to this commit we'd generate a page for each MDX document as that
avoids having to load MDX dynamically (which can be tricky) or through
static props (which causes issues with serialization).

However, Vercel (which hosts for us) has an upper limit on the number of
routes, and because of this extensive documentation, we blew passed it
with this approach.

This changes to a dynamic resolution of MDX content with an async import
in the useEffect hook. This should drastically reduce the number of
routes and make Vercel happy.

I didn't do much digging into the effects of this on SSR. If it turns
out it's causes issues, we'll deal with it at that time.
This commit is contained in:
joostdecock 2023-05-20 13:22:36 +02:00
parent 99d4e303bc
commit a4f192e090
4 changed files with 117 additions and 19 deletions

8
.gitignore vendored
View file

@ -42,14 +42,6 @@ sites/lab/pages
# but not the indexes
!sites/lab/pages/*/index.js
# dev auto-generated pages
sites/dev/pages/*
# But not these
!sites/dev/pages/404.mjs
!sites/dev/pages/_app.mjs
!sites/dev/pages/contact.mjs
!sites/dev/pages/index.mjs
# org auto-generated pages
sites/org/pages/docs/*
sites/org/pages/new/pattern/*

View file

@ -0,0 +1,105 @@
// Used in static paths
import mdxMeta from 'site/prebuild/mdx.en.js'
// Dependencies
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
// Hooks
import { useState, useEffect } from 'react'
// Components
import Head from 'next/head'
import { PageWrapper, ns } from 'shared/components/wrappers/page.mjs'
import { Spinner } from 'shared/components/spinner.mjs'
import { components } from 'shared/components/mdx/index.mjs'
import { MdxWrapper } from 'shared/components/wrappers/mdx.mjs'
//import { TocWrapper } from 'shared/components/wrappers/toc.mjs'
/*
* This page is auto-generated by the prebuild script.
* Any changes you make will be overwritten on the next (pre)build.
*
* See the page-templates folder for more info.
*/
const DocsPage = ({ page, slug }) => {
const [frontmatter, setFrontmatter] = useState({ title: 'FreeSewing.dev' })
const [MDX, setMDX] = useState(<Spinner />)
/* Load MDX dynamically */
useEffect(() => {
const loadMDX = async () => {
import(`../../../markdown/dev/${slug}/en.md`).then((mod) => {
setFrontmatter(mod.frontmatter)
const Component = mod.default
setMDX(<Component components={components} />)
})
}
loadMDX()
}, [slug])
return (
<PageWrapper {...page} title={frontmatter.title}>
<Head>
<meta property="og:title" content={frontmatter.title} key="title" />
<meta property="og:type" content="article" key="type" />
<meta property="og:description" content={``} key="type" />
<meta property="og:article:author" content="Joost De Cock" key="author" />
<meta
property="og:image"
content={`https://canary.backend.freesewing.org/og-img/en/org/${slug}}`}
key="image"
/>
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content={`https://freesewing.dev/${slug}`} key="url" />
<meta property="og:locale" content="en" key="locale" />
<meta property="og:site_name" content="freesewing.dev" key="site" />
<title>{frontmatter.title} - FreeSewing.dev</title>
</Head>
<div className="flex flex-row-reverse flex-wrap xl:flex-nowrap justify-end">
{false && frontmatter.toc && (
<div className="mb-8 w-full xl:w-80 2xl:w-96 xl:pl-8 2xl:pl-16">
{/* FIXME: Implement toc plugin to add it to the frontmatter */}
{/* <TocWrapper toc={frontmatter.toc} /> */}
</div>
)}
<MdxWrapper>{MDX}</MdxWrapper>
</div>
</PageWrapper>
)
}
export default DocsPage
/*
* getStaticProps() is used to fetch data at build-time.
* To learn more, see: https://nextjs.org/docs/basic-features/data-fetching
*/
export async function getStaticProps({ params }) {
return {
props: {
...(await serverSideTranslations('en', ['docs', ...ns])),
slug: params.slug.join('/'),
page: {
locale: 'en',
path: params.slug,
},
},
}
}
/*
* getStaticPaths() is used to specify for which routes (think URLs)
* this page should be used to generate the result.
*
* On this page, it is returning a list of routes (think URLs) for all
* the mdx (markdown) content.
* That list comes from mdxMeta, which is build in the prebuild step
* and contains paths, titles, and intro for all markdown.
*
* To learn more, see: https://nextjs.org/docs/basic-features/data-fetching
*/
export async function getStaticPaths() {
return {
paths: Object.keys(mdxMeta).map((slug) => '/' + slug),
fallback: false,
}
}

View file

@ -101,7 +101,7 @@ const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale
</div>
)
export const MdxWrapper = ({ MDX, frontmatter = {}, components = {} }) => {
export const MdxWrapper = ({ MDX = false, frontmatter = {}, components = {}, children = [] }) => {
const { t } = useTranslation('docs')
const allComponents = { ...baseComponents, ...components }
const { locale, slug } = useContext(NavigationContext)
@ -116,7 +116,7 @@ export const MdxWrapper = ({ MDX, frontmatter = {}, components = {} }) => {
lastUpdated={updates.lastUpdates}
{...{ locale, slug, t }}
/>
<MDX components={allComponents} />
{MDX ? <MDX components={allComponents} /> : children}
</div>
)
}

View file

@ -29,6 +29,12 @@ export const PageWrapper = (props) => {
// Title is typically set in props.t but check props.title too
const pageTitle = props.t ? props.t : props.title ? props.title : null
/*
* Contexts
*/
const { modalContent } = useContext(ModalContext)
const { setNavigation, slug } = useContext(NavigationContext)
/*
* This forces a re-render upon initial bootstrap of the app
* This is needed to avoid hydration errors because theme can't be set reliably in SSR
@ -38,25 +44,20 @@ export const PageWrapper = (props) => {
const [navupdates, setNavupdates] = useState(0)
useEffect(() => setCurrentTheme(theme), [currentTheme, theme])
/*
* Contexts
*/
const { modalContent } = useContext(ModalContext)
const { setNavigation } = useContext(NavigationContext)
/*
* Update navigation context with title and path
*/
useEffect(() => {
if (navupdates < 5) {
// Only update if a new page was loaded
if (path.join('/') !== 'slug') {
setNavigation({
title: pageTitle,
locale,
path,
})
setNavupdates(navupdates + 1)
} else console.warn('Suppressing navigation update in page wrapper to avoid render loop')
}, [path, pageTitle])
}
}, [path, pageTitle, slug])
/*
* Hotkeys (keyboard actions)