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,127 @@
import { drupal } from "@/lib/drupal"
import type { DrupalAboutNode } from "@/lib/types"
import { AvatarImage } from "./avatar-image"
import { MailToLink } from "./mail-to-link"
const drupalBaseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL ?? ""
const FALLBACK_TITLE = "Robert Nasarek"
function FallbackBody() {
return (
<>
<p className="mb-2 text-emerald-600 font-medium">
<span style={{ color: "#009966" }}>Data Engineer & Developer</span>
</p>
<p className="text-slate-600 leading-relaxed">
Im a freelance backend and data engineer specialising in data modelling, ETL pipelines, and data-centric application architecture. I design and implement scalable APIs and backend systems using Python and modern web frameworks like Next.js, Drupal, and Django to build robust data workflows for analytics and machine learning use cases.
</p>
<p className="text-slate-600 pt-4">
My focus is on semantic and structured data systems that turn heterogeneous sources into reliable, queryable, and reusable knowledge. I deliver production-ready solutions, including containerised deployments and reproducible data pipelines, with an emphasis on correctness, performance, and maintainability.
</p>
</>
)
}
async function getAboutPageContent(): Promise<{
title: string
body: string | null
email: string | null
}> {
if (!drupalBaseUrl) {
return {
title: FALLBACK_TITLE,
body: null,
email: null,
}
}
try {
const translatedPath = await drupal.translatePath("/about/robert-nasarek", {
withAuth: true,
next: { revalidate: 60 },
})
if (
!translatedPath?.jsonapi?.resourceName ||
!translatedPath?.entity?.uuid ||
translatedPath.jsonapi.resourceName !== "node--about"
) {
return { title: FALLBACK_TITLE, body: null, email: null }
}
const node = await drupal.getResource<DrupalAboutNode>(
"node--about",
translatedPath.entity.uuid,
{ withAuth: true, next: { revalidate: 60 } }
)
if (!node) {
return { title: FALLBACK_TITLE, body: null, email: null }
}
const bodyObj = node.body
const bodyText =
typeof bodyObj === "string"
? bodyObj
: bodyObj?.processed ?? bodyObj?.value ?? ""
return {
title: node.title ?? FALLBACK_TITLE,
body: bodyText || null,
email: node.field_email ?? null,
}
} catch (error) {
if ((error as Error).name !== "AbortError") {
console.warn("[HomeAbout] CMS unreachable:", (error as Error).message)
}
return { title: FALLBACK_TITLE, body: null, email: null }
}
}
export async function HomeAbout() {
const { title, body, email } = await getAboutPageContent()
return (
<section className="home-about animate-fade-in-on-load pb-10" aria-labelledby="about-heading">
<div className="home-about-header mb-8 text-center">
<h2
id="about-heading"
className="home-about-title mb-3 font-bold tracking-tight text-slate-900"
style={{ fontSize: "var(--fluid-section-title)" }}
>
About me
</h2>
<p
className="home-about-description mx-auto max-w-3xl text-slate-600"
style={{ fontSize: "var(--fluid-hero-desc)" }}
>
Data engineer and developer with a focus on linked open data, ontology engineering, and full-stack development.
</p>
</div>
<div className="home-about-content mx-auto max-w-4xl">
<div className="home-about-bio flex flex-col gap-6 rounded-xl border border-slate-200 bg-slate-50 p-6 sm:flex-row sm:items-start sm:gap-8 sm:p-8">
<AvatarImage alt={title} />
<div className="min-w-0 flex-1">
<h3 className="mb-2 text-xl font-semibold text-slate-900">
{title}
</h3>
<div
className="pemerald pemerald-slate text-slate-600 pemerald-a:text-emerald-600 pemerald-a:underline hover:pemerald-a:text-emerald-500 [&>p]:leading-relaxed [&>p:first-child]:mb-2 [&>p:first-child]:text-emerald-600 [&>p:first-child]:font-medium [&>p:not(:first-child)]:pt-4"
>
<p className="mb-2 text-emerald-600 font-medium">
<span style={{ color: "#009966" }}>Data Engineer & Developer</span>
</p>
<p className="text-slate-600 leading-relaxed">
Hi Im a freelance backend and data engineer specialising in data modelling, ETL pipelines, and data-centric application architecture. I design and implement scalable APIs and backend systems using Python and modern web frameworks like Next.js, Drupal, and Django to build robust data workflows for analytics and machine learning use cases.
</p>
<p className="text-slate-600 pt-4">
My focus is on semantic and structured data systems that turn heterogeneous sources into reliable, queryable, and reusable knowledge. I deliver production-ready solutions, including containerised deployments and reproducible data pipelines, with an emphasis on correctness, performance, and maintainability.
</p>
</div>
<p className="mt-4">
<MailToLink email={email} />
</p>
</div>
</div>
</div>
</section>
)
}