Skip to content
Open
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
45 changes: 22 additions & 23 deletions src/app/[...markdownPath]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import { notFound } from 'next/navigation'
import dynamic from 'next/dynamic'
import { Suspense } from 'react'
import { Suspense, cache } from 'react'
import { Layout } from '@/components/layouts/Layout'
import { createMetadata } from '@/server/createMetadata'
import { PageFrontmatter } from '@/types'
Expand Down Expand Up @@ -30,26 +30,37 @@ type Props = {
searchParams: URLSearchParams
}

export async function generateMetadata({ params }: Props) {
const directPath = `${params.markdownPath.join('/')}.mdx`
const indexPath = `${params.markdownPath.join('/')}/index.mdx`

const canonicalUrl = createCanonicalUrl(params.markdownPath)
/**
* Helper function to load MDX file for a given path.
* Cached per-request to avoid duplicate file checks and imports between generateMetadata and page component.
*/
const loadPageMdx = cache(async (markdownPath: string[]) => {
const directPath = `${markdownPath.join('/')}.mdx`
const indexPath = `${markdownPath.join('/')}/index.mdx`

const hasDirectMdx = fs.existsSync(path.join(process.cwd(), 'src/content', directPath))
const hasIndexMdx = fs.existsSync(path.join(process.cwd(), 'src/content', indexPath))

if (!hasDirectMdx && !hasIndexMdx) {
// Return a 404 page if the markdown file doesn't exist
// give Next error page
return {}
return null
}

const pageMdx = (await import(`@/content/${hasDirectMdx ? directPath : indexPath}`)) as {
default: () => JSX.Element
frontmatter?: PageFrontmatter
}

return pageMdx
})

export async function generateMetadata({ params }: Props) {
const canonicalUrl = createCanonicalUrl(params.markdownPath)
const pageMdx = await loadPageMdx(params.markdownPath)

if (!pageMdx) {
return {}
}

return await createMetadata({
title: pageMdx.frontmatter?.meta?.title ?? pageMdx.frontmatter?.title ?? '',
description: pageMdx.frontmatter?.meta?.description ?? pageMdx.frontmatter?.description ?? '',
Expand All @@ -60,26 +71,14 @@ export async function generateMetadata({ params }: Props) {
}

export default async function MarkdownPage({ params }: Props) {
const directPath = `${params.markdownPath.join('/')}.mdx`
const indexPath = `${params.markdownPath.join('/')}/index.mdx`

const canonicalUrl = createCanonicalUrl(params.markdownPath)
const sidebar = await importSidebarConfigFromMarkdownPath(params.markdownPath)
const pageMdx = await loadPageMdx(params.markdownPath)

const hasDirectMdx = fs.existsSync(path.join(process.cwd(), 'src/content', directPath))
const hasIndexMdx = fs.existsSync(path.join(process.cwd(), 'src/content', indexPath))

if (!hasDirectMdx && !hasIndexMdx) {
// Return a 404 page if the markdown file doesn't exist
// give Next error page
if (!pageMdx) {
notFound()
}

const pageMdx = (await import(`@/content/${hasDirectMdx ? directPath : indexPath}`)) as {
default: () => JSX.Element
frontmatter?: PageFrontmatter
}

const techArticleSchema = {
'@context': 'https://schema.org',
'@type': 'TechArticle',
Expand Down
8 changes: 5 additions & 3 deletions src/server/getAllContent.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import fg from 'fast-glob'
import { cache } from 'react'

/**
* Gets all content MDX files from the content directory via fast-glob
* Gets all content MDX files from the content directory via fast-glob.
* Cached per-request to avoid duplicate file system scans.
*/
export const getAllContent = async () => {
export const getAllContent = cache(async () => {
const files = await fg('src/content/**/*.mdx')

return files
}
})
6 changes: 4 additions & 2 deletions src/server/getAllContentPaths.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { cache } from 'react'
import { getAllContent } from './getAllContent'

/**
* Get all content paths from the content directory
* MDX paths are replaced to URL paths
* f.e. src/content/about/license.mdx => /about/license
* Cached per-request to avoid duplicate processing.
*/
export const getAllContentPaths = async (options?: { withoutIndex?: boolean }) => {
export const getAllContentPaths = cache(async (options?: { withoutIndex?: boolean }) => {
const allFiles = await getAllContent()

const allPaths = allFiles
Expand All @@ -27,4 +29,4 @@ export const getAllContentPaths = async (options?: { withoutIndex?: boolean }) =
})

return allPaths
}
})
41 changes: 38 additions & 3 deletions src/server/getAllMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import fs from 'fs'
import path from 'path'
import glob from 'fast-glob'
import frontmatter from 'front-matter'
import { cache } from 'react'
import { PageMeta } from '@/types'

/**
* Loads ALL metadata - should only be used for build-time operations like sitemap generation.
* WARNING: This reads all 558 MDX files and should NOT be called during regular page requests.
*/
export async function getAllMetadata() {
let pages = await glob('**/*.mdx', { cwd: 'src/content' })

Expand All @@ -26,6 +31,36 @@ export async function getAllMetadata() {
return allMetadata
}

export const getMetadataFromPath = async (path: string) => {
return (await getAllMetadata())[path] || undefined
}
/**
* Lazy loads metadata for a single file path instead of all files.
* Only reads the specific file needed, avoiding loading all 558 MDX files.
* Cached per-request to avoid duplicate reads.
*/
export const getMetadataFromPath = cache(async (urlPath: string) => {
// Convert URL path to possible file paths
const directPath = `${urlPath.substring(1)}.mdx` // Remove leading slash
const indexPath = `${urlPath.substring(1)}/index.mdx`

// Try direct path first, then index path
let filePath: string | null = null
const directFullPath = path.join(process.cwd(), 'src/content', directPath)
const indexFullPath = path.join(process.cwd(), 'src/content', indexPath)

if (fs.existsSync(directFullPath)) {
filePath = directFullPath
} else if (fs.existsSync(indexFullPath)) {
filePath = indexFullPath
}

if (!filePath) {
return undefined
}

// Read only this single file
const mdxContent = fs.readFileSync(filePath, 'utf-8')
const { attributes } = frontmatter(mdxContent) as {
attributes: { meta: PageMeta | undefined }
}

return attributes.meta
})
34 changes: 23 additions & 11 deletions src/server/getExtensions.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import fs from 'fs'
import path from 'path'
import { glob } from 'fast-glob'
import { ExtensionMetaWithUrl } from '@/types'

export const getExtensions = async (path: string = '') => {
let pages = (await glob(`**/*.mdx`, { cwd: `src/${path}` })).filter((p) => {
import { cache } from 'react'
import frontmatter from 'front-matter'
import { ExtensionMetaWithUrl, PageFrontmatter } from '@/types'

/**
* Gets all extensions from MDX files.
* Reads frontmatter directly from files instead of compiling MDX for better performance.
* Cached per-request to avoid duplicate file reads.
*/
export const getExtensions = cache(async (pathParam: string = '') => {
let pages = (await glob(`**/*.mdx`, { cwd: `src/${pathParam}` })).filter((p) => {
return !p.endsWith('index.mdx') && !p.endsWith('overview.mdx')
})

const pathPrefix = path ? `${path}/` : ''
const pathPrefix = pathParam ? `${pathParam}/` : ''

let allExtensions = (await Promise.all(
pages.map(async (page) => {
let pagePath = `/${pathPrefix + page}`

// eslint-disable-next-line @next/next/no-assign-module-variable
const module = await import(`@/content/${pagePath.replace('/content/', '')}`)
const extensionData = module.frontmatter?.extension as ExtensionMetaWithUrl | undefined
const pageTags = module.frontmatter?.tags || []
// Read frontmatter directly without compiling MDX - much faster and less memory
const filePath = path.join(process.cwd(), 'src', pathPrefix + page)
const mdxContent = fs.readFileSync(filePath, 'utf-8')
const { attributes } = frontmatter(mdxContent) as { attributes: PageFrontmatter }

const extensionData = attributes.extension as ExtensionMetaWithUrl | undefined
const pageTags = attributes.tags || []

if (!extensionData) {
return null
Expand Down Expand Up @@ -65,7 +77,7 @@ export const getExtensions = async (path: string = '') => {
}

return [
path + pagePath,
pathParam + pagePath,
{
...extensionData,
path: page,
Expand All @@ -90,4 +102,4 @@ export const getExtensions = async (path: string = '') => {
const extensionData = Object.fromEntries(allExtensions)

return extensionData
}
})
9 changes: 7 additions & 2 deletions src/server/getIncidents.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs'
import path from 'path'
import fm from 'front-matter'
import { cache } from 'react'
import { IncidentData, PageFrontmatter } from '@/types'

// Helper function to safely parse dates
Expand All @@ -13,7 +14,11 @@ function parseDateSafely(dateString: string): Date | null {
return isNaN(date.getTime()) ? null : date
}

export async function getIncidents(): Promise<IncidentData[]> {
/**
* Gets all incidents from MDX files.
* Cached per-request to avoid duplicate file system scans.
*/
export const getIncidents = cache(async (): Promise<IncidentData[]> => {
const incidentsDir = path.join(process.cwd(), 'src/content/resources/incidents')

try {
Expand Down Expand Up @@ -71,4 +76,4 @@ export async function getIncidents(): Promise<IncidentData[]> {
console.error('Error loading incidents:', error)
return []
}
}
})
30 changes: 22 additions & 8 deletions src/server/getUIComponents.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import fs from 'fs'
import path from 'path'
import { glob } from 'fast-glob'
import { UIComponentMetaWithUrl } from '@/types'
import { cache } from 'react'
import frontmatter from 'front-matter'
import { UIComponentMetaWithUrl, PageFrontmatter } from '@/types'

export const getUIComponents = async (path: string = '') => {
let pages = (await glob(`**/*.mdx`, { cwd: `src/${path}` })).filter((p) => {
/**
* Gets all UI components from MDX files.
* Reads frontmatter directly from files instead of compiling MDX for better performance.
* Cached per-request to avoid duplicate file reads.
*/
export const getUIComponents = cache(async (pathParam: string = '') => {
let pages = (await glob(`**/*.mdx`, { cwd: `src/${pathParam}` })).filter((p) => {
return !p.endsWith('index.mdx') && !p.endsWith('overview.mdx')
})

const pathPrefix = path ? `${path}/` : ''
const pathPrefix = pathParam ? `${pathParam}/` : ''

let allComponents = (await Promise.all(
pages.map(async (page) => {
const pagePath = `/${pathPrefix + page}`
const componentData = (await import(`@/content/${pagePath.replace('/content/', '')}`))
.frontmatter?.component as UIComponentMetaWithUrl | undefined

// Read frontmatter directly without compiling MDX - much faster and less memory
const filePath = path.join(process.cwd(), 'src', pathPrefix + page)
const mdxContent = fs.readFileSync(filePath, 'utf-8')
const { attributes } = frontmatter(mdxContent) as { attributes: PageFrontmatter }

const componentData = attributes.component as UIComponentMetaWithUrl | undefined

if (!componentData) {
return null
}

return [
path + pagePath,
pathParam + pagePath,
{
...componentData,
path: page,
Expand All @@ -39,4 +53,4 @@ export const getUIComponents = async (path: string = '') => {

const componentsData = Object.fromEntries(allComponents)
return componentsData
}
})