Add Drupal headless stack with Next.js frontend
- Add Next.js frontend service (nextjs) with Dockerfile and source - Update docker-compose.yml: image names, Drupal 11.3.3, nextjs service - Add docker-compose.override.yml.disabled for dev hot-reload - Add install-headless-modules.sh for OAuth/JSON:API module setup - Add README.md with full setup and configuration guide - Update nginx/Dockerfile and nginx.conf.template for cms. subdomain - Update drupal/Dockerfile PHP-FPM build args - Gitignore **/.vscode/ to prevent IDE workspace files from being tracked
This commit is contained in:
parent
71a8dac389
commit
f8b8f53d54
85 changed files with 7802 additions and 17 deletions
76
drupal/nextjs/components/main-nav.tsx
Normal file
76
drupal/nextjs/components/main-nav.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import type { DrupalMenuItem } from "next-drupal"
|
||||
import { MainNavClient } from "./main-nav-client"
|
||||
|
||||
const drupalBaseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL ?? ""
|
||||
|
||||
interface RawMenuItem {
|
||||
id: string
|
||||
parent: string
|
||||
title: string
|
||||
url: string
|
||||
enabled?: boolean
|
||||
weight?: string | number
|
||||
}
|
||||
|
||||
function buildMenuTree(
|
||||
items: RawMenuItem[],
|
||||
parentId: string
|
||||
): DrupalMenuItem[] {
|
||||
return items
|
||||
.filter((item) => (item.parent || "") === parentId && item.enabled !== false)
|
||||
.sort((a, b) => Number(a.weight ?? 0) - Number(b.weight ?? 0))
|
||||
.map((item) => {
|
||||
const children = buildMenuTree(items, item.id)
|
||||
return {
|
||||
...item,
|
||||
items: children.length ? children : undefined,
|
||||
} as DrupalMenuItem
|
||||
})
|
||||
}
|
||||
|
||||
const FALLBACK_MENU: DrupalMenuItem[] = [
|
||||
{
|
||||
id: "home",
|
||||
title: "Home",
|
||||
url: "/",
|
||||
enabled: true,
|
||||
items: undefined,
|
||||
} as DrupalMenuItem,
|
||||
]
|
||||
|
||||
async function getMainMenu(): Promise<DrupalMenuItem[]> {
|
||||
if (!drupalBaseUrl) return FALLBACK_MENU
|
||||
|
||||
try {
|
||||
const url = `${drupalBaseUrl.replace(/\/$/, "")}/jsonapi/menu_items/main`
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000)
|
||||
const res = await fetch(url, {
|
||||
headers: { Accept: "application/vnd.api+json" },
|
||||
next: { revalidate: 60 },
|
||||
signal: controller.signal,
|
||||
})
|
||||
clearTimeout(timeoutId)
|
||||
if (!res.ok) {
|
||||
if (res.status !== 404) {
|
||||
console.warn(`[MainNav] Menu fetch returned ${res.status}, using fallback nav.`)
|
||||
}
|
||||
return FALLBACK_MENU
|
||||
}
|
||||
|
||||
const json = await res.json()
|
||||
const items: RawMenuItem[] = json.data ?? []
|
||||
|
||||
return buildMenuTree(items, "")
|
||||
} catch (error) {
|
||||
if ((error as Error).name !== "AbortError") {
|
||||
console.warn("[MainNav] CMS unreachable, using fallback nav:", (error as Error).message)
|
||||
}
|
||||
return FALLBACK_MENU
|
||||
}
|
||||
}
|
||||
|
||||
export async function MainNav() {
|
||||
const menuItems = await getMainMenu()
|
||||
return <MainNavClient menuItems={menuItems} />
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue