diff --git a/.changeset/friendly-windows-count.md b/.changeset/friendly-windows-count.md
new file mode 100644
index 00000000000..8cc7f1f3f1d
--- /dev/null
+++ b/.changeset/friendly-windows-count.md
@@ -0,0 +1,5 @@
+---
+"nextra": patch
+---
+
+fix route group within `app/` dir crash the `convertToPageMap`
diff --git a/docs/app/_components/i18n-demo.tsx b/docs/app/_components/i18n-demo.tsx
index 43ba7ef74e6..3e47f857b04 100644
--- a/docs/app/_components/i18n-demo.tsx
+++ b/docs/app/_components/i18n-demo.tsx
@@ -34,7 +34,6 @@ export const I18n: FC = () => {
setActive(lang)}
- // eslint-disable-next-line tailwindcss/no-custom-classname -- TODO: configure eslint-plugin-tailwindcss to import nextra-theme-docs styles so below classes could be removed
className={cn(
'relative cursor-default px-4 py-1.5 whitespace-nowrap select-none',
active === lang
diff --git a/examples/docs/src/app/showcase/(overview)/page.jsx b/examples/docs/src/app/showcase/(overview)/page.jsx
new file mode 100644
index 00000000000..f075557cbf1
--- /dev/null
+++ b/examples/docs/src/app/showcase/(overview)/page.jsx
@@ -0,0 +1,20 @@
+/**
+ * @see https://github.com/shuding/nextra/issues/4148
+ */
+
+export const metadata = {}
+
+export default function Page() {
+ return (
+
+ Showcase
+
+ )
+}
diff --git a/packages/eslint-config/src/index.ts b/packages/eslint-config/src/index.ts
index d912f99a170..41512e0efdc 100644
--- a/packages/eslint-config/src/index.ts
+++ b/packages/eslint-config/src/index.ts
@@ -3,7 +3,7 @@ import { includeIgnoreFile } from '@eslint/compat'
import js from '@eslint/js'
// @ts-expect-error -- no types
import eslintPluginNext from '@next/eslint-plugin-next'
-import type { Linter } from 'eslint'
+// import type { Linter } from 'eslint'
// @ts-expect-error -- no types
import eslintConfigPrettier from 'eslint-config-prettier'
import eslintPluginImport from 'eslint-plugin-import-x'
@@ -13,24 +13,24 @@ import * as eslintPluginReactCompiler from 'eslint-plugin-react-compiler'
// @ts-expect-error -- no types
import eslintPluginReactHooks from 'eslint-plugin-react-hooks'
import eslintPluginSonarJs from 'eslint-plugin-sonarjs'
-// @ts-expect-error -- no types
-import eslintPluginTailwindCss from 'eslint-plugin-tailwindcss'
+// import eslintPluginTailwindCss from 'eslint-plugin-tailwindcss'
// @ts-expect-error -- no types
import eslintPluginTsSortKeys from 'eslint-plugin-typescript-sort-keys'
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import tseslint from 'typescript-eslint'
import type { Config } from 'typescript-eslint'
-const TAILWIND_CONFIG = {
- extends: [eslintPluginTailwindCss.configs['flat/recommended']],
- rules: {
- 'tailwindcss/classnames-order': 'off', // conflicts with prettier-plugin-tailwindcss
- 'tailwindcss/enforces-negative-arbitrary-values': 'error',
- 'tailwindcss/enforces-shorthand': 'error',
- 'tailwindcss/migration-from-tailwind-2': 'error',
- 'tailwindcss/no-custom-classname': 'error'
- } satisfies Linter.RulesRecord
-}
+// TODO: Enable once `eslint-plugin-tailwindcss` will support Tailwind CSS v4
+// const TAILWIND_CONFIG = {
+// extends: [eslintPluginTailwindCss.configs['flat/recommended']],
+// rules: {
+// 'tailwindcss/classnames-order': 'off', // conflicts with prettier-plugin-tailwindcss
+// 'tailwindcss/enforces-negative-arbitrary-values': 'error',
+// 'tailwindcss/enforces-shorthand': 'error',
+// 'tailwindcss/migration-from-tailwind-2': 'error',
+// 'tailwindcss/no-custom-classname': 'error'
+// } satisfies Linter.RulesRecord
+// }
const REACT_COMPILER_RESTRICT = {
name: 'react',
@@ -201,7 +201,7 @@ const config: Config = tseslint.config(
},
// ⚙️ nextra-theme-docs
{
- ...TAILWIND_CONFIG,
+ // ...TAILWIND_CONFIG,
files: ['packages/nextra-theme-docs/**'],
settings: {
tailwindcss: {
@@ -217,7 +217,7 @@ const config: Config = tseslint.config(
}
},
rules: {
- ...TAILWIND_CONFIG.rules,
+ // ...TAILWIND_CONFIG.rules,
'no-restricted-imports': [
'error',
{ name: 'next/link', message: 'Use `
` instead' },
@@ -229,10 +229,10 @@ const config: Config = tseslint.config(
},
// ⚙️ nextra-theme-blog
{
- ...TAILWIND_CONFIG,
+ // ...TAILWIND_CONFIG,
files: ['packages/nextra-theme-blog/**'],
rules: {
- ...TAILWIND_CONFIG.rules,
+ // ...TAILWIND_CONFIG.rules,
'no-restricted-imports': [
'error',
{
@@ -252,7 +252,7 @@ const config: Config = tseslint.config(
},
// ⚙️ nextra
{
- ...TAILWIND_CONFIG,
+ // ...TAILWIND_CONFIG,
files: ['packages/nextra/**'],
settings: {
tailwindcss: {
@@ -267,7 +267,7 @@ const config: Config = tseslint.config(
}
},
rules: {
- ...TAILWIND_CONFIG.rules,
+ // ...TAILWIND_CONFIG.rules,
'import/extensions': ['error', 'ignorePackages'],
// False positive due Tailwind CSS v4
'tailwindcss/no-custom-classname': 'off'
@@ -275,7 +275,7 @@ const config: Config = tseslint.config(
},
// ⚙️ Docs
{
- ...TAILWIND_CONFIG,
+ // ...TAILWIND_CONFIG,
files: ['docs/**'],
settings: {
tailwindcss: {
@@ -307,7 +307,7 @@ const config: Config = tseslint.config(
},
// ⚙️ SWR-site example
{
- ...TAILWIND_CONFIG,
+ // ...TAILWIND_CONFIG,
files: ['examples/swr-site/**'],
settings: {
tailwindcss: {
diff --git a/packages/nextra/src/server/__tests__/to-page-map.test.ts b/packages/nextra/src/server/__tests__/to-page-map.test.ts
index ee10c2436f2..758b7c8795b 100644
--- a/packages/nextra/src/server/__tests__/to-page-map.test.ts
+++ b/packages/nextra/src/server/__tests__/to-page-map.test.ts
@@ -873,6 +873,7 @@ describe('generatePageMap()', () => {
"src/app/_meta.js",
"src/app/blog/page.jsx",
"src/app/page.jsx",
+ "src/app/showcase/(overview)/page.jsx",
"src/content/_meta.js",
"src/content/advanced/code-highlighting.mdx",
"src/content/features/_meta.js",
@@ -1011,6 +1012,11 @@ describe('generatePageMap()', () => {
"name": "index",
"route": "/",
},
+ {
+ "__pagePath": "src/app/showcase/(overview)/page.jsx",
+ "name": "showcase",
+ "route": "/showcase",
+ },
{
"children": [
{
@@ -1220,6 +1226,11 @@ describe('generatePageMap()', () => {
"name": "index",
"route": "/",
},
+ {
+ "__pagePath": "src/app/showcase/(overview)/page.jsx",
+ "name": "showcase",
+ "route": "/showcase",
+ },
]
`)
})
diff --git a/packages/nextra/src/server/page-map/to-page-map.ts b/packages/nextra/src/server/page-map/to-page-map.ts
index a17bd036184..676030561cb 100644
--- a/packages/nextra/src/server/page-map/to-page-map.ts
+++ b/packages/nextra/src/server/page-map/to-page-map.ts
@@ -1,4 +1,5 @@
import path from 'node:path'
+import { normalizeAppPath } from 'next/dist/shared/lib/router/utils/app-paths.js'
import type { TItem } from '../../types.js'
interface NestedMap {
@@ -54,7 +55,9 @@ export function convertToPageMap({
//
// will be normalized to:
// app/posts/aaron-swartz-a-programmable-web/page.mdx
- dir.replaceAll(/\(.*?\)(\/|$)/g, '')
+ //
+ // The `normalizeAppPath` function ensures a leading slash is present, so we slice it off.
+ normalizeAppPath(dir).slice(1)
: [dir, name !== 'index' && name].filter(Boolean).join('/')
pages[key] = filePath
}