@lexibase/rdx-next 5.1.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,8 +50,8 @@ through `@lexibase/rdx-ui`: logo, navbar, Base UI version dropdown, mobile/deskt
50
50
  sidebar, landing page, MDX styles and theme provider. The adapter owns content
51
51
  and route data; it does not implement a parallel UI theme.
52
52
 
53
- Use `homepage: { enabled: true }` in `rdx.config.ts` for the centered-logo
54
- homepage, or import a project-owned React component with `homepage.component`.
53
+ Use `homepage: { enabled: true }` in `rdx.config.ts` for the built-in landing
54
+ page, or import a project-owned React component with `homepage.component`.
55
55
  Without the option, `/` redirects to `/docs`. Additional content sources from
56
56
  explicitly configured plugins enable the shared navbar dropdown.
57
57
 
package/bin/cli.mjs CHANGED
@@ -26,7 +26,7 @@ export async function runRdxCli({ templateDir } = {}) {
26
26
  templateDir ?? path.join(packageRoot, 'templates', 'runtime')
27
27
  const runtimeDirectory = path.join(projectRoot, '.rdx')
28
28
 
29
- async function prepareRuntime(project) {
29
+ async function prepareRuntime(project, homepageTranslations) {
30
30
  await mkdir(runtimeDirectory, { recursive: true })
31
31
  await preparePublicAssets(projectRoot, runtimeDirectory)
32
32
  await cp(runtimeTemplate, runtimeDirectory, {
@@ -35,6 +35,11 @@ export async function runRdxCli({ templateDir } = {}) {
35
35
  })
36
36
  await prepareProjectStyles(projectRoot, runtimeDirectory)
37
37
  await prepareLocaleRoutes(project, runtimeDirectory, runtimeTemplate)
38
+ if (homepageTranslations) {
39
+ const { prepareHomepageTranslations } =
40
+ await import('./homepage-translations.mjs')
41
+ await prepareHomepageTranslations(runtimeDirectory, homepageTranslations)
42
+ }
38
43
  // Resolve in Node before Next processes this file: UI may be transitive,
39
44
  // and native Tailwind plugins must stay outside the Turbopack module graph.
40
45
  const resolvePlugin = createRequire(import.meta.url)
@@ -140,6 +145,20 @@ export async function runRdxCli({ templateDir } = {}) {
140
145
  rootDir: projectRoot,
141
146
  command,
142
147
  })
148
+ let homepageTranslations
149
+ if (
150
+ project.config.homepage?.enabled &&
151
+ project.config.homepage.translations &&
152
+ (supportedCommands.has(command) ||
153
+ command === 'check' ||
154
+ command === 'prepare')
155
+ ) {
156
+ const { readHomepageTranslations } =
157
+ await import('./homepage-translations.mjs')
158
+ homepageTranslations = await readHomepageTranslations(project, {
159
+ report: command === 'check' || command === 'build',
160
+ })
161
+ }
143
162
  if (command === 'check' || command === 'build') {
144
163
  const { checkDocumentation, formatDocumentationCheck } =
145
164
  await import('@lexibase/rdx-core/check')
@@ -153,7 +172,7 @@ export async function runRdxCli({ templateDir } = {}) {
153
172
  if (command === 'check') return
154
173
  }
155
174
  if (command === 'prepare') {
156
- await prepareRuntime(project)
175
+ await prepareRuntime(project, homepageTranslations)
157
176
  console.log('RDX runtime prepared in .rdx/')
158
177
  return
159
178
  }
@@ -174,7 +193,7 @@ export async function runRdxCli({ templateDir } = {}) {
174
193
  return
175
194
  }
176
195
 
177
- await prepareRuntime(project)
196
+ await prepareRuntime(project, homepageTranslations)
178
197
  const projectRequire = createRequire(path.join(projectRoot, 'package.json'))
179
198
  const nextCli = projectRequire.resolve('next/dist/bin/next')
180
199
  const child = spawn(
@@ -0,0 +1,137 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ const sourceFile = 'components/home.tsx'
5
+ const metadataKey = '$metadata'
6
+
7
+ function missingFile(error) {
8
+ return error.code === 'ENOENT' || error.code === 'ENOTDIR'
9
+ }
10
+
11
+ async function readOptional(file) {
12
+ try {
13
+ return await readFile(file, 'utf8')
14
+ } catch (error) {
15
+ if (!missingFile(error)) throw error
16
+ }
17
+ }
18
+
19
+ async function sourcePhrases(rootDir) {
20
+ const source = await readOptional(path.join(rootDir, sourceFile))
21
+ if (source === undefined)
22
+ throw new Error(`Create ${sourceFile} to use homepage.translations.`)
23
+ const { default: ts } = await import('typescript')
24
+ const file = ts.createSourceFile(sourceFile, source, ts.ScriptTarget.Latest)
25
+ for (const statement of file.statements) {
26
+ if (
27
+ !ts.isExpressionStatement(statement) ||
28
+ !ts.isStringLiteral(statement.expression)
29
+ )
30
+ break
31
+ if (statement.expression.text === 'use client')
32
+ throw new Error(
33
+ `${sourceFile} must be a Server Component when homepage.translations is enabled. Move 'use client' into interactive child components.`
34
+ )
35
+ }
36
+ const phrases = new Set()
37
+ function visit(node) {
38
+ if (
39
+ ts.isCallExpression(node) &&
40
+ ts.isIdentifier(node.expression) &&
41
+ node.expression.text === 't' &&
42
+ node.arguments.length === 1 &&
43
+ ts.isStringLiteralLike(node.arguments[0])
44
+ )
45
+ phrases.add(node.arguments[0].text)
46
+ ts.forEachChild(node, visit)
47
+ }
48
+ visit(file)
49
+ return phrases
50
+ }
51
+
52
+ export async function readHomepageTranslations(
53
+ project,
54
+ { report = false } = {}
55
+ ) {
56
+ const { rootDir, localization } = project
57
+ const phrases = await sourcePhrases(rootDir)
58
+ phrases.delete(metadataKey)
59
+ const catalogs = []
60
+ for (const locale of localization?.locales ?? []) {
61
+ if (!locale.folder) continue
62
+ const file = `locale/${locale.folder}/home.json`
63
+ const raw = await readOptional(path.join(rootDir, file))
64
+ let messages = {}
65
+ if (raw !== undefined) {
66
+ try {
67
+ const parsed = JSON.parse(raw)
68
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
69
+ throw new Error('Expected a JSON object.')
70
+ messages = parsed
71
+ catalogs.push({ file, locale: locale.path })
72
+ } catch {
73
+ console.warn(
74
+ `[RDX] ${file}: expected a JSON object of homepage phrases. Using the primary text.`
75
+ )
76
+ }
77
+ }
78
+ if (!report) continue
79
+ for (const phrase of phrases) {
80
+ if (
81
+ !Object.hasOwn(messages, phrase) ||
82
+ typeof messages[phrase] !== 'string' ||
83
+ !messages[phrase].trim()
84
+ )
85
+ console.warn(
86
+ `[RDX] ${file}: missing translation for ${JSON.stringify(phrase)}`
87
+ )
88
+ }
89
+ for (const phrase of Object.keys(messages)) {
90
+ if (phrase !== metadataKey && !phrases.has(phrase))
91
+ console.warn(
92
+ `[RDX] ${file}: orphaned translation for ${JSON.stringify(phrase)}`
93
+ )
94
+ }
95
+ }
96
+ return { catalogs, phrases }
97
+ }
98
+
99
+ export async function prepareHomepageTranslations(
100
+ runtime,
101
+ { catalogs, phrases }
102
+ ) {
103
+ const imports = catalogs.map(
104
+ ({ file }, index) =>
105
+ `import messages${index} from ${JSON.stringify(`../${file}`)}`
106
+ )
107
+ const entries = catalogs.map(
108
+ ({ locale }, index) => ` [${JSON.stringify(locale)}, messages${index}],`
109
+ )
110
+ await writeFile(
111
+ path.join(runtime, 'rdx-home.ts'),
112
+ `import translatedComponent from '../components/home'
113
+ ${imports.join('\n')}
114
+
115
+ const catalogs = new Map<string, Record<string, unknown>>([
116
+ ${entries.join('\n')}
117
+ ])
118
+
119
+ const phrases: string[] = ${JSON.stringify([...phrases])}
120
+
121
+ export function getHomepageTranslation(locale?: string) {
122
+ return { translatedComponent, messages: locale ? catalogs.get(locale) : undefined }
123
+ }
124
+
125
+ export function getHomepageSeo(locale?: string) {
126
+ const translatedLocales = new Set<string>()
127
+ for (const [path, catalog] of catalogs) {
128
+ if (phrases.some((phrase) => {
129
+ const text = Object.hasOwn(catalog, phrase) ? catalog[phrase] : undefined
130
+ return typeof text === 'string' && text.trim() && text.trim() !== phrase.trim()
131
+ })) translatedLocales.add(path)
132
+ }
133
+ return { translatedLocales, metadata: locale ? catalogs.get(locale)?.${metadataKey} : undefined }
134
+ }
135
+ `
136
+ )
137
+ }
@@ -11,6 +11,7 @@ const generated = [
11
11
  ...defaultFiles.map((file) => `app/(rdx-default)/${file}`),
12
12
  'app/(rdx-default)/[...notFound]/page.tsx',
13
13
  'app/(rdx-locales)/[locale]/layout.tsx',
14
+ 'app/(rdx-locales)/[locale]/page.tsx',
14
15
  'app/(rdx-locales)/[locale]/not-found.tsx',
15
16
  'app/(rdx-locales)/[locale]/docs/[[...slug]]/page.tsx',
16
17
  'app/(rdx-locales)/[locale]/rdx-search/[source]/route.ts',
@@ -83,7 +84,7 @@ export async function prepareLocaleRoutes(project, runtime, template) {
83
84
  await emit(
84
85
  `app/(rdx-default)/${file}`,
85
86
  text
86
- .replace(/from '(\.\.\/[^']*rdx-project)'/g, "from '../$1'")
87
+ .replace(/from '(\.\.\/[^']*rdx-(?:home|project))'/g, "from '../$1'")
87
88
  .replace("'./globals.css'", "'../globals.css'")
88
89
  )
89
90
  }
@@ -107,6 +108,27 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
107
108
  export default async function Layout({ children, params }: { children: React.ReactNode, params: Promise<{ locale: string }> }) {
108
109
  return <RdxLayout config={await configuration(params)} bodyClassName="antialiased">{children}</RdxLayout>
109
110
  }
111
+ `
112
+ )
113
+ await emit(
114
+ 'app/(rdx-locales)/[locale]/page.tsx',
115
+ `import { HomePage, getHomepageMetadata, getLocalizedProject } from '${provider}'
116
+ import { notFound, permanentRedirect } from 'next/navigation'
117
+ import { getHomepageSeo, getHomepageTranslation } from '../../../rdx-home'
118
+ import { getProject } from '../../../rdx-project'
119
+ export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
120
+ const project = await getProject()
121
+ const { locale } = await params
122
+ return getHomepageMetadata({ project, locale, ...getHomepageSeo(locale) })
123
+ }
124
+ export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
125
+ const project = await getProject()
126
+ const { locale } = await params
127
+ const selected = project.localization?.locales.find(entry => entry.path === locale)
128
+ if (!project.config.homepage?.enabled || !selected) notFound()
129
+ if (selected.code === project.localization!.defaultLocale) permanentRedirect('/')
130
+ return <HomePage project={getLocalizedProject(project, locale)} {...getHomepageTranslation(locale)} />
131
+ }
110
132
  `
111
133
  )
112
134
  await emit(
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { defineConfig } from '@lexibase/rdx-core/config';
2
- export { RDXConfig, RdxHomepage, RdxMetadata, RdxPlugin, RdxPluginContext, RdxRuntimeConfig } from '@lexibase/rdx-types';
2
+ export { RDXConfig, RdxHomepage, RdxHomepageProps, RdxMetadata, RdxPlugin, RdxPluginContext, RdxRuntimeConfig, RdxTranslatedHomepageProps } from '@lexibase/rdx-types';
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { RdxProject, RDXConfig, RdxContentSource, RdxDocPage } from '@lexibase/rdx-types';
2
+ import { ComponentType, ReactNode } from 'react';
3
+ import { RdxProject, RDXConfig, RdxContentSource, RdxDocPage, RdxTranslatedHomepageProps } from '@lexibase/rdx-types';
4
4
  export { createRdxProject, getLocalizedProject, getProjectDocsPaths } from '@lexibase/rdx-core';
5
5
  import { MetadataRoute, Metadata } from 'next';
6
6
 
@@ -32,6 +32,12 @@ declare function getMarkdownResponse(project: RdxProject, path: string[]): Respo
32
32
  declare function markdownNotFound(): Response;
33
33
  declare function getDocumentMarkdownResponse(project: RdxProject, source: RdxContentSource | undefined, slug: string, documentPath?: DocumentPath): Response;
34
34
 
35
+ declare function getHomepageMetadata({ project, locale, translatedLocales, metadata, }: {
36
+ project: RdxProject;
37
+ locale?: string;
38
+ translatedLocales?: ReadonlySet<string>;
39
+ metadata?: unknown;
40
+ }): Metadata;
35
41
  /** Read metadata from the same content source and index used by the page. */
36
42
  declare function getDocumentMetadata({ project, slug, }: {
37
43
  project: RdxProject;
@@ -47,8 +53,10 @@ declare function RdxLayout({ children, config, bodyClassName, }: {
47
53
  config: RDXConfig;
48
54
  bodyClassName?: string;
49
55
  }): react.JSX.Element;
50
- declare function HomePage({ project }: {
56
+ declare function HomePage({ project, translatedComponent: TranslatedHome, messages, }: {
51
57
  project: RdxProject;
58
+ translatedComponent?: ComponentType<RdxTranslatedHomepageProps>;
59
+ messages?: Record<string, unknown>;
52
60
  }): react.JSX.Element;
53
61
  declare function NotFoundPage({ project }: {
54
62
  project: RdxProject;
@@ -58,4 +66,4 @@ declare function DocsPage({ slug, project, }: {
58
66
  project: RdxProject;
59
67
  }): Promise<react.JSX.Element>;
60
68
 
61
- export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMarkdownResponse, getDocumentMetadata, getLlmIndexResponse, getMarkdownHref, getMarkdownPaths, getMarkdownResponse, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots, markdownNotFound, parseMarkdownPath };
69
+ export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMarkdownResponse, getDocumentMetadata, getHomepageMetadata, getLlmIndexResponse, getMarkdownHref, getMarkdownPaths, getMarkdownResponse, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots, markdownNotFound, parseMarkdownPath };
@@ -1,6 +1,7 @@
1
1
  // src/runtime/index.tsx
2
2
  import {
3
3
  getDocument as getDocument4,
4
+ getDocsIndex as getDocsIndex4,
4
5
  getDocsPaths,
5
6
  getSourceOptions as getSourceOptions5,
6
7
  resolveDocsRoute as resolveDocsRoute2,
@@ -17,7 +18,7 @@ import { DocsSidebar } from "@lexibase/rdx-ui/components/docs-sidebar";
17
18
  import { DocsToc } from "@lexibase/rdx-ui/components/docs-toc";
18
19
  import { DocsPageTransition } from "@lexibase/rdx-ui/components/docs-page-transition";
19
20
  import { DocsSearch } from "@lexibase/rdx-ui/components/docs-search";
20
- import { Home } from "@lexibase/rdx-ui/components/home";
21
+ import { Home, HomeSection } from "@lexibase/rdx-ui/components/home";
21
22
  import { SkipLink } from "@lexibase/rdx-ui/components/skip-link";
22
23
  import { FooterCredit } from "@lexibase/rdx-ui/components/footer-credit";
23
24
  import { EditPageLink } from "@lexibase/rdx-ui/components/edit-page-link";
@@ -317,6 +318,67 @@ import {
317
318
  getSourceOptions as getSourceOptions4
318
319
  } from "@lexibase/rdx-core";
319
320
  import { notFound } from "next/navigation";
321
+ function getHomepageMetadata({
322
+ project,
323
+ locale,
324
+ translatedLocales,
325
+ metadata
326
+ }) {
327
+ const { config, localization } = project;
328
+ if (!config.homepage?.enabled || !localization) return {};
329
+ const selected = localization.locales.find((entry) => entry.path === locale);
330
+ if (locale && (!selected || selected.code === localization.defaultLocale))
331
+ return {};
332
+ const base = config.metadata.metadataBase;
333
+ const primaryUrl = base ? new URL("/", base).href : void 0;
334
+ if (locale && !translatedLocales?.has(locale)) {
335
+ if (config.homepage.component) return {};
336
+ return {
337
+ robots: { index: false, follow: true },
338
+ ...primaryUrl ? { alternates: { canonical: primaryUrl } } : {}
339
+ };
340
+ }
341
+ const url = base && new URL(locale ? `/${locale}` : "/", base).href;
342
+ const site = getRdxMetadata(config);
343
+ const fields = metadata && typeof metadata === "object" ? metadata : {};
344
+ const title = "title" in fields && typeof fields.title === "string" ? fields.title.trim() : "";
345
+ const description = "description" in fields && typeof fields.description === "string" ? fields.description.trim() : "";
346
+ const defaults = {
347
+ title: config.metadata.applicationName,
348
+ description: config.description
349
+ };
350
+ const social = {
351
+ ...title ? { title: { absolute: title } } : {},
352
+ ...description ? { description } : {}
353
+ };
354
+ return {
355
+ ...social,
356
+ ...site.openGraph || title || description ? {
357
+ openGraph: {
358
+ ...defaults,
359
+ ...site.openGraph,
360
+ ...social,
361
+ ...url ? { url } : {}
362
+ }
363
+ } : {},
364
+ ...site.twitter || title || description ? { twitter: { ...defaults, ...site.twitter, ...social } } : {},
365
+ ...base && url && primaryUrl ? {
366
+ alternates: {
367
+ canonical: url,
368
+ languages: {
369
+ [localization.defaultLocale]: primaryUrl,
370
+ "x-default": primaryUrl,
371
+ ...Object.fromEntries(
372
+ localization.locales.filter((entry) => translatedLocales?.has(entry.path)).map((entry) => [
373
+ entry.code,
374
+ new URL(`/${entry.path}`, base).href
375
+ ])
376
+ )
377
+ }
378
+ }
379
+ } : {}
380
+ };
381
+ }
320
382
  function getDocumentMetadata({
321
383
  project,
322
384
  slug
@@ -429,9 +491,13 @@ function RdxLayout({
429
491
  }
430
492
  ) }) });
431
493
  }
494
+ function homepageHref(project, locale) {
495
+ return locale && locale.code !== project.localization?.defaultLocale ? `/${locale.path}` : "/";
496
+ }
432
497
  function RdxHeader({
433
498
  project,
434
- slug
499
+ slug,
500
+ onHomepage = false
435
501
  }) {
436
502
  const { config } = project;
437
503
  const messages = getMessages(config.lang);
@@ -460,14 +526,18 @@ function RdxHeader({
460
526
  ) : void 0,
461
527
  siteTitle: config.metadata.applicationName,
462
528
  logo: /* @__PURE__ */ jsx(BrandLogo, { source: config.navbar?.logo, className: "size-5" }),
463
- homeHref: config.homepage?.enabled ? "/" : getDocsHref4(project),
529
+ homeHref: config.homepage?.enabled ? homepageHref(project, project.locale) : getDocsHref4(project),
464
530
  docsHref: getDocsHref4(project, source),
465
531
  languageMenu: project.localization ? /* @__PURE__ */ jsx(
466
532
  LanguageDropdown,
467
533
  {
468
534
  lang: config.lang,
469
535
  current: project.locale?.code ?? project.localization.defaultLocale,
470
- options: getLanguageLinks(project, source, currentSlug),
536
+ options: onHomepage ? project.localization.locales.map((locale) => ({
537
+ code: locale.code,
538
+ label: locale.label,
539
+ href: homepageHref(project, locale)
540
+ })) : getLanguageLinks(project, source, currentSlug),
471
541
  contributionUrl: project.localization.contributionUrl
472
542
  }
473
543
  ) : void 0,
@@ -477,32 +547,79 @@ function RdxHeader({
477
547
  }
478
548
  );
479
549
  }
480
- function HomePage({ project }) {
550
+ function HomePage({
551
+ project,
552
+ translatedComponent: TranslatedHome,
553
+ messages
554
+ }) {
481
555
  const { config } = project;
482
- if (!config.homepage?.enabled) redirect(getDocsHref4(project));
556
+ const docsHref = getDocsHref4(project);
557
+ if (!config.homepage?.enabled) redirect(docsHref);
483
558
  const homepage = config.homepage;
484
559
  const CustomHome = homepage.component;
560
+ const docsLabel = getMessages(config.lang).docs;
561
+ const source = project.sources[0];
562
+ const categories = CustomHome || TranslatedHome ? [] : getDocsIndex4(getSourceOptions5(project, source));
563
+ const firstPage = categories[0]?.pages[0];
485
564
  return /* @__PURE__ */ jsxs(Fragment, { children: [
486
- /* @__PURE__ */ jsx(RdxHeader, { project }),
565
+ /* @__PURE__ */ jsx(RdxHeader, { project, onHomepage: true }),
487
566
  /* @__PURE__ */ jsx(
488
567
  "main",
489
568
  {
490
569
  id: "rdx-main",
491
570
  tabIndex: -1,
492
- className: "flex-1 min-h-0 overflow-y-auto",
493
- children: CustomHome ? /* @__PURE__ */ jsx(CustomHome, {}) : /* @__PURE__ */ jsx(
571
+ className: "relative flex-1 min-h-0 overflow-y-auto",
572
+ children: CustomHome ? /* @__PURE__ */ jsx(CustomHome, { lang: config.lang, docsHref }) : TranslatedHome ? /* @__PURE__ */ jsx(
573
+ TranslatedHome,
574
+ {
575
+ lang: config.lang,
576
+ docsHref,
577
+ t: (text2) => {
578
+ const translation = messages && Object.hasOwn(messages, text2) ? messages[text2] : void 0;
579
+ return typeof translation === "string" && translation.trim() ? translation : text2;
580
+ }
581
+ }
582
+ ) : /* @__PURE__ */ jsx(
494
583
  Home,
495
584
  {
496
585
  title: homepage.title ?? config.metadata.applicationName,
497
- logo: /* @__PURE__ */ jsx(
498
- BrandLogo,
586
+ logo: /* @__PURE__ */ jsx(BrandLogo, { source: config.navbar?.logo, className: "size-5" }),
587
+ description: homepage.description ?? config.description,
588
+ buttons: homepage.buttons?.map((button) => ({
589
+ ...button,
590
+ href: project.locale && /^\/docs(?:[/?#]|$)/.test(button.href) ? `/${project.locale.path}${button.href}` : button.href
591
+ })) ?? [{ label: docsLabel, href: docsHref }],
592
+ previewTitle: firstPage?.title,
593
+ previewDescription: firstPage?.description,
594
+ children: categories.length > 0 && /* @__PURE__ */ jsx(
595
+ HomeSection,
499
596
  {
500
- source: config.navbar?.logo,
501
- className: "size-52 hover:scale-105 hover:rotate-1 transition-[scale,rotation] duration-1000"
597
+ "aria-labelledby": "home-docs-title",
598
+ className: "border-t bg-muted/20",
599
+ children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-6xl", children: [
600
+ /* @__PURE__ */ jsx(
601
+ "h2",
602
+ {
603
+ id: "home-docs-title",
604
+ className: "text-3xl font-semibold tracking-tight sm:text-4xl",
605
+ children: docsLabel
606
+ }
607
+ ),
608
+ /* @__PURE__ */ jsx("ul", { className: "mt-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: categories.map((category) => {
609
+ const page = category.pages[0];
610
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
611
+ Button,
612
+ {
613
+ href: getDocsHref4(project, source, page.slug),
614
+ variant: "link",
615
+ className: "h-auto whitespace-normal p-0 text-left",
616
+ children: category.title
617
+ }
618
+ ) }, category.id);
619
+ }) })
620
+ ] })
502
621
  }
503
- ),
504
- description: homepage.description,
505
- buttons: homepage.buttons
622
+ )
506
623
  }
507
624
  )
508
625
  }
@@ -765,6 +882,7 @@ export {
765
882
  createRdxProject,
766
883
  getDocumentMarkdownResponse,
767
884
  getDocumentMetadata,
885
+ getHomepageMetadata,
768
886
  getLlmIndexResponse,
769
887
  getLocalizedProject4 as getLocalizedProject,
770
888
  getMarkdownHref,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexibase/rdx-next",
3
- "version": "5.1.2",
3
+ "version": "6.0.0",
4
4
  "description": "The Next.js runtime for RDX documentation projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,8 +52,8 @@
52
52
  "next-mdx-remote": "^6.0.0",
53
53
  "remark-gfm": "^4",
54
54
  "typescript": "6.0.3",
55
- "@lexibase/rdx-core": "3.1.0",
56
- "@lexibase/rdx-types": "2.4.0"
55
+ "@lexibase/rdx-core": "3.2.0",
56
+ "@lexibase/rdx-types": "2.6.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@lexibase/rdx-ui": "^3.5.0",
@@ -81,9 +81,9 @@
81
81
  "next": "^16.4.2",
82
82
  "react": "19.3.1",
83
83
  "react-dom": "19.3.1",
84
- "@lexibase/rdx-ui": "3.5.3",
84
+ "@rdx/eslint-config": "1.0.0",
85
85
  "@rdx/typescript-config": "1.0.0",
86
- "@rdx/eslint-config": "1.0.0"
86
+ "@lexibase/rdx-ui": "3.5.5"
87
87
  },
88
88
  "scripts": {
89
89
  "dev": "tsup src/index.ts src/runtime/index.tsx --format esm --dts --watch --external @lexibase/rdx-core --external @lexibase/rdx-types --external @lexibase/rdx-ui --external next --external react --external react-dom --external next-mdx-remote",
@@ -1,7 +1,15 @@
1
- import { HomePage } from '@lexibase/rdx-next/runtime'
1
+ import { getHomepageMetadata, HomePage } from '@lexibase/rdx-next/runtime'
2
2
 
3
+ import { getHomepageSeo, getHomepageTranslation } from '../rdx-home'
3
4
  import { getProject } from '../rdx-project'
4
5
 
6
+ export async function generateMetadata() {
7
+ return getHomepageMetadata({
8
+ project: await getProject(),
9
+ ...getHomepageSeo(),
10
+ })
11
+ }
12
+
5
13
  export default async function Home() {
6
- return <HomePage project={await getProject()} />
14
+ return <HomePage project={await getProject()} {...getHomepageTranslation()} />
7
15
  }
@@ -0,0 +1,2 @@
1
+ export const getHomepageTranslation: (locale?: string) => object = () => ({})
2
+ export const getHomepageSeo: (locale?: string) => object = () => ({})