Testimonials on /todo-cards, /newsletter, /challenge now come from the new Payload testimonials collection via a shared TestimonialsGrid component, instead of 3 separately hardcoded arrays. Adds Next.js Draft Mode (/api/preview) plus Live-Preview-aware client wrappers (LiveRichText, LiveTestimonialsGrid, LivePostContent) for posts, legal pages, and testimonials — mounted only while Draft Mode is enabled, so ordinary visitors keep getting the plain static components.
einfach-produktiv — Frontend
Next.js frontend for einfach-produktiv.mk360.de,
Coolify-managed and deployed from this repo (git.mk360.de/Marco/einfach-produktiv).
For the VPS-wide infrastructure this app runs on (Caddy, Coolify, Gitea, the
shared Payload instance), see ~/dev/README.md — this file only covers what's
specific to this project.
Stack
- Next.js 16.2.9 (App Router,
output: "standalone"for a small Docker image — seeAGENTS.mdbefore touching anything version-specific, this Next.js release differs from older training-data conventions) - React 19.2.4, Tailwind CSS 4, Motion for animation
- No local database, no auth — all editable content comes from the shared
Payload CMS at
payload.mk360.de(see below)
Getting started
npm install
npm run dev
Open http://localhost:3000. npm run build && npm run start reproduces the production build locally — do this before pushing,
since Coolify builds with --no-cache and a failed build only surfaces there
otherwise.
Environment variables: PAYLOAD_URL (defaults to https://payload.mk360.de
if unset, see app/lib/payload.ts). Set in Coolify's app settings for
production, not in a committed .env — this app has no other secrets.
Pages
| Route | Purpose |
|---|---|
/ |
Home — hero, product spotlight, tools grid, trust row |
/shop |
Product grid (all active products for this tenant) |
/blog, /blog/[slug] |
Blog overview + post detail |
/cart |
Cart (localStorage-backed, see below) |
/checkout |
Shipping + payment method selection, order summary |
/bestellbestaetigung |
Order confirmation — reads the one-time snapshot /checkout wrote |
/challenge |
"Mini-Challenge" tool |
/todo-cards |
"Todo-Karten" tool |
/newsletter |
Newsletter signup |
/versand |
Shipping policy (timeframes, costs) |
/impressum, /datenschutz, /agb, /widerruf |
Legal pages (from Payload, see legal-pages below) |
Content backend: Payload CMS
This app is one tenant in a shared, multi-tenant Payload instance also
used by other projects on the same VPS (see docker/payload/ in the infra
repo). All content queries go through app/lib/payload.ts, which hardcodes
TENANT_SLUG = "einfach-produktiv" and filters every request with
where[tenant.slug][equals]=einfach-produktiv — the tenant field itself is
injected automatically into every collection below by Payload's
multiTenantPlugin, not defined in this app.
/api/products is this app's own same-origin proxy route (app/api/products/route.ts)
in front of getProducts() — used by client components (cart, related
products) that need the catalog reactively, so they don't talk to Payload's
API directly and reuse Next.js's fetch cache instead of an extra round trip.
Collections used by this tenant
All reads are public (access.read: () => true); only writes are
admin-gated in the Payload admin UI at payload.mk360.de/admin.
| Collection (slug) | Used for | Key fields |
|---|---|---|
products |
/shop grid, homepage spotlight, cart, checkout |
name, slug (cart item id — not Payload's numeric id, so existing localStorage carts survive catalog changes), description, price, compareAtPrice (optional strikethrough), image, detailHref, sortOrder, spotlight + spotlightEyebrow/spotlightHeadline/spotlightText/spotlightImage (homepage "Neu im Shop" section — falls back to image if no dedicated spotlight image is set) |
posts |
/blog, /blog/[slug] |
title, slug, category (relation to categories), excerpt, thumbnail, content (richText), readTime (auto-calculated on save from word count), featured (shown as the /blog hero post; most-recently-published wins if several are marked), publishedAt, quoteLabel (label + icon + underline shown next to every blockquote in content, default "Merke dir:" — leave empty to hide that framing, the blockquote text itself still renders), relatedProduct (optional relation to products, powers the "Passend dazu" card at the end of the post — leave empty to hide that card, or empty if the linked product has no detailHref) |
categories |
Blog post categorization | name, slug (unique per tenant, not globally) |
legal-pages |
/impressum, /datenschutz, /agb, /widerruf |
type (impressum/datenschutz/agb/widerruf, one doc per type per tenant), title, content (richText), attachment (optional file, e.g. the Muster-Widerrufsformular PDF) |
trust-badges |
Horizontal "Schneller Versand / Versandkostenfrei / Mit Liebe verpackt" row — shown on /shop, /cart, /checkout, /widerruf, /agb, 404 |
title, description (supports {{lieferzeit}}/{{kostenfreiab}} placeholders, resolved by the frontend from shipping-settings/shipping-methods at render time — not by Payload itself), icon, sortOrder |
cart-trust-badges |
Sidebar bullets on /cart (title only) and /checkout (title + description) — deliberately a separate collection from trust-badges so the two pages can't drift into showing different claims |
title, description, icon, sortOrder |
shipping-methods |
/checkout shipping selection |
title (no day-range in the title text — that lives in shipping-settings now, keeping both in one title used to drift), description, price, freeShippingThreshold (per-method, optional — leave empty for a method that should never be free, e.g. Express), active (inactive methods are hidden, not shown disabled), sortOrder |
shipping-settings |
Delivery-time disclosure shown on /shop, homepage spotlight, ToDo-Karten, /cart, /checkout, /versand (Art. 246a §1 Abs.1 Nr.8 EGBGB requires this visible before checkout) |
handlingDaysMin/handlingDaysMax (processing time before it ships), transitDaysMin/transitDaysMax (carrier time) — the app derives the combined total itself. One row per tenant. |
payment-methods |
/checkout payment selection |
title, icons (array — e.g. 3 logos for "Kreditkarte"), active, sortOrder |
werkzeuge-cards |
Homepage "Meine Werkzeuge" 3-card grid | title, description, icon, ctaLabel, ctaHref, sortOrder |
media |
Shared upload collection backing every image/icon/thumbnail/attachment field above |
alt (required for images), title (optional display name for download links) |
All of the above (except media, users, tenants) are grouped in the
Payload admin sidebar under Commerce (products, shipping-methods,
shipping-settings, payment-methods, trust-badges, cart-trust-badges)
or Content (posts, categories, legal-pages, werkzeuge-cards);
media/users/tenants sit under Platform.
Editorial changes (prices, copy, images, toggling a shipping/payment method
on or off) all happen in the Payload admin UI — no code deploy needed. Adding
a new field to any collection above requires editing the collection file in
docker/payload/src/collections/ and a migration, which does need a deploy
of the Payload service.
Cart & checkout — demo status
- Cart (
app/lib/cart.ts) is entirely client-side, stored inlocalStorageunderep_cart, keyed by each product'sslug. - Checkout has no real backend.
/checkout's "Jetzt kaufen" click writes a one-time snapshot (chosen shipping/payment method, cart contents) tosessionStorage(app/lib/order.ts), which/bestellbestaetigungreads once and displays — that snapshot is the order record. There is no payment processing, no persisted order in Payload or anywhere else, and no confirmation email yet. Treat this as a frontend/demo checkout flow, not a functioning store.
Deployment
- Dockerfile: 3-stage build (
deps→builder→runner) with BuildKit cache mounts, producing a ~100 MB standalone image. This is the referenceDockerfilecopied when scaffolding new projects on this VPS (see the infra repo's new-project workflow). - Deploys via
git push→ Gitea webhook → Caddy/deploy/einfach-produktivbridge → Coolify API → rebuild + restart. Full mechanics in~/dev/README.md.
Related design source
~/dev/einfach-produktiv/mockups/— Figma-stage mockup PNGs~/dev/einfach-produktiv/styleguide.md— design tokens (colors, type, spacing)~/dev/einfach-produktiv/einfachproduktiv_figma_prompt_guide_v3.md— Figma rebuild prompt guide