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:
rnsrk 2026-03-30 11:14:17 +02:00
parent 71a8dac389
commit f8b8f53d54
85 changed files with 7802 additions and 17 deletions

View file

@ -0,0 +1,89 @@
import { drupal } from "@/lib/drupal"
import type { DrupalNode } from "@/lib/types"
import { NodeArticle } from "@/components/node-article"
import { notFound } from "next/navigation"
import type { Metadata } from "next"
const drupalBaseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL ?? ""
interface NodePageProps {
params: Promise<{
slug: string[]
}>
}
// Render dynamically at runtime (not at build time).
export const dynamic = "force-dynamic"
export const revalidate = 60
export async function generateMetadata({
params,
}: NodePageProps): Promise<Metadata> {
if (!drupalBaseUrl) return {}
const { slug } = await params
const path = drupal.constructPathFromSegment(slug)
try {
const translatedPath = await drupal.translatePath(path, { withAuth: true })
if (!translatedPath?.jsonapi?.resourceName || !translatedPath?.entity?.uuid) {
return {}
}
const node = await drupal.getResource<DrupalNode>(
translatedPath.jsonapi.resourceName,
translatedPath.entity.uuid,
{
withAuth: true,
params: {
"fields[node--article]": "title",
"fields[node--page]": "title",
"fields[node--about]": "title",
},
}
)
return {
title: node?.title,
}
} catch {
return {}
}
}
export default async function NodePage({ params }: NodePageProps) {
if (!drupalBaseUrl) notFound()
const { slug } = await params
const path = drupal.constructPathFromSegment(slug)
try {
const translatedPath = await drupal.translatePath(path, { withAuth: true })
if (!translatedPath?.jsonapi?.resourceName || !translatedPath?.entity?.uuid) {
notFound()
}
const type = translatedPath.jsonapi.resourceName
const node = await drupal.getResource<DrupalNode>(
type,
translatedPath.entity.uuid,
{
withAuth: true,
params: {
include: "uid",
},
}
)
if (!node || !node.status) {
notFound()
}
return <NodeArticle node={node} />
} catch {
notFound()
}
}