diff --git a/.changeset/spicy-swans-check.md b/.changeset/spicy-swans-check.md
new file mode 100644
index 0000000000..a233d175db
--- /dev/null
+++ b/.changeset/spicy-swans-check.md
@@ -0,0 +1,7 @@
+---
+'nextra': patch
+'nextra-theme-blog': patch
+'nextra-theme-docs': patch
+---
+
+feat(nextra/docs/blog): allow import `.md`/`.mdx` as well
diff --git a/examples/swr-site/components/external.mdx b/examples/swr-site/components/external.mdx
new file mode 100644
index 0000000000..df768c14ff
--- /dev/null
+++ b/examples/swr-site/components/external.mdx
@@ -0,0 +1,5 @@
+import Callout from 'nextra-theme-docs/callout'
+
+
+ This `` comes from `external.mdx`
+
diff --git a/examples/swr-site/pages/docs/advanced/markdown-import.en-US.mdx b/examples/swr-site/pages/docs/advanced/markdown-import.en-US.mdx
new file mode 100644
index 0000000000..13cc753cd2
--- /dev/null
+++ b/examples/swr-site/pages/docs/advanced/markdown-import.en-US.mdx
@@ -0,0 +1,26 @@
+import External from '../../../components/external.mdx'
+import IndexPage from '../../index.en-US.mdx'
+
+# Markdown import
+
+## Import of markdown file
+
+```mdx
+import External from '../../../components/external.mdx'
+
+
+```
+
+
+
+## Import of another page
+
+You can also import complete pages:
+
+```mdx filename="not-index.md"
+import IndexPage from '../../index.en-US.mdx'
+
+
+```
+
+
diff --git a/examples/swr-site/pages/docs/advanced/meta.en-US.json b/examples/swr-site/pages/docs/advanced/meta.en-US.json
index f46023fe70..d303499f98 100644
--- a/examples/swr-site/pages/docs/advanced/meta.en-US.json
+++ b/examples/swr-site/pages/docs/advanced/meta.en-US.json
@@ -11,6 +11,7 @@
"cache": "Cache",
"performance": "Performance",
"react-native": "React Native",
+ "markdown-import": "Markdown import",
"more": "More: A Super Super Super Super Long Directory",
"file-name.with.DOTS": "Filenames with dots"
}
diff --git a/packages/nextra-theme-blog/src/index.tsx b/packages/nextra-theme-blog/src/index.tsx
index c97aa21ab8..d6e3ef7212 100644
--- a/packages/nextra-theme-blog/src/index.tsx
+++ b/packages/nextra-theme-blog/src/index.tsx
@@ -14,7 +14,6 @@ import getTags from './utils/get-tags'
import sortDate from './utils/sort-date'
import type { PageMapItem, PageOpt } from 'nextra'
import type { NextraBlogTheme } from './types'
-const isProduction = process.env.NODE_ENV === 'production'
// comments
const Cusdis = dynamic(
// @ts-ignore
@@ -217,27 +216,13 @@ const createLayout = (opts: PageOpt, _config: NextraBlogTheme) => {
},
_config
)
- let layoutUsed = false
- const Page = ({ children }: { children: React.ReactChildren }) => {
- if (!layoutUsed && isProduction) {
- throw new Error(
- '[Nextra] Please add the `getLayout` logic to your _app.js, see https://nextjs.org/docs/basic-features/layouts#per-page-layouts.'
- )
- }
- return children
- }
- const Layout = (page: React.ReactChildren) => {
- layoutUsed = true
- return (
-
-
-
- )
- }
+
+ const Page = ({ children }: { children: React.ReactChildren }) => children
+ const Layout = (page: React.ReactChildren) => (
+
+
+
+ )
Page.getLayout = Layout
return Page
}
diff --git a/packages/nextra-theme-docs/src/index.tsx b/packages/nextra-theme-docs/src/index.tsx
index 3a19dbc456..8f9363115f 100644
--- a/packages/nextra-theme-docs/src/index.tsx
+++ b/packages/nextra-theme-docs/src/index.tsx
@@ -22,8 +22,6 @@ import './polyfill'
import Breadcrumb from './breadcrumb'
import renderComponent from './utils/render-component'
-const isProduction = process.env.NODE_ENV === 'production'
-
function useDirectoryInfo(pageMap: PageMapItem[]) {
const { locale, defaultLocale, asPath } = useRouter()
@@ -258,18 +256,10 @@ interface DocsLayoutProps extends PageOpt {
}
const createLayout = (opts: DocsLayoutProps, _config: DocsThemeConfig) => {
const extendedConfig = Object.assign({}, defaultConfig, _config, opts)
- let layoutUsed = false
- const Page = ({ children }: { children: React.ReactChildren }) => {
- if (!layoutUsed && isProduction) {
- throw new Error(
- '[Nextra] Please add the `getLayout` logic to your _app.js, see https://nextjs.org/docs/basic-features/layouts#per-page-layouts.'
- )
- }
- return children
- }
- Page.getLayout = (page: any) => {
- layoutUsed = true
- return (
+
+ const Page = ({ children }: { children: React.ReactChildren }) => children
+
+ Page.getLayout = (page: any) => (
{
)
- }
+
return Page
}
diff --git a/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap b/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap
index 0ac2d3799c..ab0cbf8a8d 100644
--- a/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap
+++ b/packages/nextra/__test__/__snapshots__/page-map.test.ts.snap
@@ -52,6 +52,11 @@ exports[`Page Process > pageMap en-US 1`] = `
"name": "file-name.with.DOTS",
"route": "/docs/advanced/file-name.with.DOTS",
},
+ {
+ "locale": "en-US",
+ "name": "markdown-import",
+ "route": "/docs/advanced/markdown-import",
+ },
{
"locale": "en-US",
"meta": {
@@ -66,6 +71,7 @@ exports[`Page Process > pageMap en-US 1`] = `
},
"cache": "Cache",
"file-name.with.DOTS": "Filenames with dots",
+ "markdown-import": "Markdown import",
"more": "More: A Super Super Super Super Long Directory",
"performance": "Performance",
"react-native": "React Native",
@@ -434,6 +440,11 @@ exports[`Page Process > pageMap zh-CN 1`] = `
"name": "react-native",
"route": "/docs/advanced/react-native",
},
+ {
+ "locale": "en-US",
+ "name": "markdown-import",
+ "route": "/docs/advanced/markdown-import",
+ },
],
"name": "advanced",
"route": "/docs/advanced",
diff --git a/packages/nextra/src/loader.ts b/packages/nextra/src/loader.ts
index 4d3f410fe0..d8813c3c0a 100644
--- a/packages/nextra/src/loader.ts
+++ b/packages/nextra/src/loader.ts
@@ -10,7 +10,7 @@ import { addPage } from './content-dump'
import { parseFileName } from './utils'
import { compileMdx } from './compile'
import { getPageMap, findPagesDir } from './page-map'
-import { collectFiles } from './plugin'
+import { collectFiles, collectMdx } from './plugin'
import { MARKDOWN_EXTENSION_REGEX, IS_PRODUCTION } from './constants'
// TODO: create this as a webpack plugin.
@@ -67,6 +67,12 @@ async function loader(
? pageMapCache.get()!
: await collectFiles(pagesDir, '/')
+ // mdx is imported but is outside the `pages` directory
+ if (!fileMap[resourcePath]) {
+ fileMap[resourcePath] = await collectMdx(resourcePath)
+ context.addMissingDependency(resourcePath)
+ }
+
const [pageMap, route, title] = getPageMap(
resourcePath,
pageMapResult,
@@ -74,11 +80,7 @@ async function loader(
defaultLocale
)
- if (!IS_PRODUCTION) {
- // Add the entire directory `pages` as the dependency
- // so we can generate the correct page map.
- context.addContextDependency(pagesDir)
- } else {
+ if (IS_PRODUCTION) {
// We only add meta files as dependencies for production build,
// so we can do incremental builds.
Object.entries(fileMap).forEach(([filePath, { name, meta, locale }]) => {
@@ -90,6 +92,10 @@ async function loader(
context.addDependency(filePath)
}
})
+ } else {
+ // Add the entire directory `pages` as the dependency,
+ // so we can generate the correct page map.
+ context.addContextDependency(pagesDir)
}
// Extract frontMatter information if it exists
diff --git a/packages/nextra/src/plugin.ts b/packages/nextra/src/plugin.ts
index 149de985fc..21cdb935b7 100644
--- a/packages/nextra/src/plugin.ts
+++ b/packages/nextra/src/plugin.ts
@@ -13,6 +13,19 @@ import { MARKDOWN_EXTENSION_REGEX } from './constants'
const readdir = promisify(fs.readdir)
const readFile = promisify(fs.readFile)
+export const collectMdx = async (filePath: string, route = '') => {
+ const { name, locale } = parseFileName(filePath)
+
+ const content = await readFile(filePath, 'utf8')
+ const { data } = grayMatter(content)
+ return {
+ name,
+ route,
+ locale,
+ ...(Object.keys(data).length && { frontMatter: data })
+ }
+}
+
export async function collectFiles(
dir: string,
route = '/',
@@ -24,33 +37,26 @@ export async function collectFiles(
await Promise.all(
files.map(async f => {
const filePath = path.resolve(dir, f.name)
- const { name, locale, ext } = parseFileName(f.name)
+ const { name, locale, ext } = parseFileName(filePath)
const fileRoute = slash(path.join(route, name.replace(/^index$/, '')))
if (f.isDirectory()) {
- if (fileRoute === '/api') return null
- const { items: children } = await collectFiles(
- filePath,
- fileRoute,
- fileMap
- )
- if (!children || !children.length) return null
+ if (fileRoute === '/api') return
+ const { items } = await collectFiles(filePath, fileRoute, fileMap)
+ if (!items.length) return
return {
name: f.name,
- children,
+ children: items,
route: fileRoute
}
- } else if (MARKDOWN_EXTENSION_REGEX.test(ext)) {
- const content = await readFile(filePath, 'utf8')
- const { data } = grayMatter(content)
- fileMap[filePath] = {
- name,
- route: fileRoute,
- locale,
- ...(Object.keys(data).length && { frontMatter: data })
- }
+ }
+
+ if (MARKDOWN_EXTENSION_REGEX.test(ext)) {
+ fileMap[filePath] = await collectMdx(filePath, fileRoute)
return fileMap[filePath]
- } else if (ext === '.json' && name === 'meta') {
+ }
+
+ if (ext === '.json' && name === 'meta') {
const content = await readFile(filePath, 'utf8')
fileMap[filePath] = {
name: 'meta.json',