From 1f99781458a8509898964e4138b8aea759a1f895 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 27 Dec 2025 22:17:35 +0100 Subject: [PATCH] init --- .env | 4 + plans/wordpress-to-nextjs-concept.md | 151 +++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 .env create mode 100644 plans/wordpress-to-nextjs-concept.md diff --git a/.env b/.env new file mode 100644 index 00000000..7276ecce --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +WOOCOMMERCE_URL=https://klz-cables.com +WOOCOMMERCE_CONSUMER_KEY=ck_38d97df86880e8fefbd54ab5cdf47a9c5a9e5b39 +WOOCOMMERCE_CONSUMER_SECRET=cs_d675ee2ac2ec7c22de84ae5451c07e42b1717759 +WORDPRESS_APP_PASSWORD=DlJH 49dp fC3a Itc3 Sl7Z Wz0k \ No newline at end of file diff --git a/plans/wordpress-to-nextjs-concept.md b/plans/wordpress-to-nextjs-concept.md new file mode 100644 index 00000000..296dd52c --- /dev/null +++ b/plans/wordpress-to-nextjs-concept.md @@ -0,0 +1,151 @@ +# WordPress → Next.js Static Migration Concept (DE/EN) + +## Goals +- Rebuild the current WordPress site (Salient + WPBakery) as a **static** Next.js site (React + TypeScript) hosted on Vercel. +- Preserve content types: **Pages**, **Blog Posts**, **WooCommerce Products (catalog-only)**. +- Add **contact via form + email** using [`Resend`](plans/wordpress-to-nextjs-concept.md:1) and [`Turnstile`](plans/wordpress-to-nextjs-concept.md:1). +- Add **analytics** using [`Vercel Analytics`](plans/wordpress-to-nextjs-concept.md:1) with **GDPR opt-in consent**. + +## Key Constraints & Decisions +- Access: SSH/SFTP, server DB credentials, and ability to run [`wp-cli`](plans/wordpress-to-nextjs-concept.md:1). +- WordPress permalinks: `/%postname%/` (posts currently at `/{postSlug}`). +- Target routes (approved): + - Pages: `/{slug}` + - Blog: `/blog` and `/blog/{postSlug}` + - Products: `/product/{productSlug}` + - Product categories: `/product-category/{slug}` +- Redirect decision (approved): 301 all posts `/{postSlug}` → `/blog/{postSlug}`. +- Languages: **German + English** (new requirement). + +## Proposed Next.js Architecture (Static + Vercel) +- Next.js App Router with **static generation** for all routes. +- Build-time data sourcing from versioned JSON snapshots (no runtime WP calls). +- Use serverless only for form submission endpoint. +- Consent-gated analytics initialization. + +### Routing Strategy with DE/EN (Polylang) +Because Pages must remain at `/{slug}`, the cleanest multilingual scheme is: + +**Option A (recommended): default locale unprefixed + secondary locale prefixed** +- Default locale (unprefixed): `/{slug}`, `/blog/{postSlug}`, `/product/{productSlug}`, `/product-category/{slug}` +- Secondary locale (prefixed): `/{locale}/{slug}`, `/{locale}/blog/{postSlug}`, `/{locale}/product/{productSlug}`, `/{locale}/product-category/{slug}` + +Given your note that **English is likely default**, the concrete mapping becomes: +- English (default): `/{slug}`, `/blog/{postSlug}`, `/product/{productSlug}`, `/product-category/{slug}` +- German: `/de/{slug}`, `/de/blog/{postSlug}`, `/de/product/{productSlug}`, `/de/product-category/{slug}` + +**Option B: both locales prefixed** +- `/en/...` and `/de/...` for all routes (this breaks the “Pages at /{slug}” requirement unless we accept redirects from root) + +Default recommendation: **Option A**, because it preserves existing URLs for the default language and minimizes redirects. + +### SEO for i18n +- Add `hreflang` alternate links per page/post/product between DE and EN. +- Canonical URLs per locale. +- Locale-aware sitemap(s) and robots. + +## Content Model (Canonical) +All entities carry a `locale` and a stable cross-locale `translationKey`. + +### Page +- `id`, `translationKey`, `locale` +- `slug`, `path` +- `titleHtml`, `contentHtml`, `excerptHtml?` +- `featuredImage?` (asset reference) +- `updatedAt` + +### Post +- `id`, `translationKey`, `locale` +- `slug`, `path` (`/blog/{slug}` or `/de/blog/{slug}`) +- `titleHtml`, `contentHtml`, `excerptHtml` +- `featuredImage?` +- `datePublished`, `updatedAt` + +### Product (WooCommerce catalog-only) +- `id`, `translationKey`, `locale` +- `slug`, `path` (`/product/{slug}` or `/de/product/{slug}`) +- `name` +- `shortDescriptionHtml`, `descriptionHtml` +- `images[]`, `featuredImage?` +- `sku?`, `regularPrice?`, `salePrice?`, `currency` (global config allowed) +- `stockStatus?` +- `categories[]` (references), `attributes[]` (for filtering) +- `variations[]` (if present) +- `updatedAt` + +### ProductCategory +- `id`, `translationKey`, `locale` +- `slug`, `name`, `path` (`/product-category/{slug}` or `/de/product-category/{slug}`) + +### Nav / Redirect / Asset +- Nav: locale-aware arrays (primary/footer) +- Redirect rules: explicit list to avoid page/post collisions, plus per-locale patterns +- Asset map: WordPress URL → local `/public` path, with optional width/height/alt + +## WPBakery/Salient Migration Strategy (Fast Path) +- Export already-rendered HTML from WordPress REST (`content.rendered`) where possible. +- Detect unexpanded shortcode remnants (`[vc_row]`, `[nectar_*]`) and re-render via server-side shortcode expansion when exporting (WordPress is the renderer). +- Normalize/sanitize HTML at build time (strip scripts/handlers, preserve structural classes/data attributes). +- Add a small compatibility CSS layer for common WPBakery layout classes (grid/rows/columns) so pages render correctly without Salient JS. + +## Multilingual Source of Truth (Polylang, DE/EN) +Polylang is confirmed. The remaining ambiguity is which language is the **default** (unprefixed) locale; you suspect it is English. + +### Export strategy (Polylang) +- Prefer exporting each locale separately via WP REST + Polylang language filter (commonly `?lang=en` and `?lang=de`). +- For products and product categories, use WooCommerce endpoints and/or WP REST endpoints with language filtering (implementation must verify which endpoints are language-aware on this site). +- If any endpoint cannot be filtered by `lang`, fall back to `wp-cli`/DB to: + - determine the language for each entity + - determine the translation group and build a stable `translationKey` + +Deliverable from this step: a stable `translationKey` shared between DE and EN versions of the same item, enabling `hreflang` and the locale switcher. + +## Export Runbook (wp-cli + REST) +Primary principle: **create repeatable, versioned snapshots** so theme rebuild can proceed offline. + +### Snapshot outputs +- `data/raw/{timestamp}/pages.{locale}.json` +- `data/raw/{timestamp}/posts.{locale}.json` +- `data/raw/{timestamp}/products.{locale}.json` +- `data/raw/{timestamp}/product-categories.{locale}.json` +- `data/raw/{timestamp}/menus.{locale}.json` +- `data/raw/{timestamp}/media.json` + mirrored files under `/public/media/...` + +### Commands / Techniques +- Use [`wp option get`](plans/wordpress-to-nextjs-concept.md:1) and WooCommerce options to confirm bases. +- Use REST for structured content (pages/posts/products/categories) when possible. +- Use DB/wp-cli for menus and translation mappings if REST doesn’t expose them. +- Media mirroring: download referenced attachments and build asset manifest. + +## Redirect Strategy (DE/EN-aware) +Base requirement: `/{postSlug}` → `/blog/{postSlug}` (301). + +With i18n Option A (English default, German under `/de/*`): +- English posts (current WP): `/{postSlug}` → `/blog/{postSlug}` +- German posts (if they exist at `/de/{postSlug}`): `/de/{postSlug}` → `/de/blog/{postSlug}` + +All redirects should be generated from export manifests to avoid false positives (pages must win at root). + +## Minimal Page Set (approved) +- Blog index + post detail +- Product category pages +- No blog tag/category archives; no product tag pages + +## Implementation Notes (Static + Forms + Analytics) +- Contact form: + - Next.js route handler posts to server endpoint. + - Validate input, verify Turnstile token, rate-limit, send via Resend. +- Consent banner: + - Store consent in local storage/cookie. + - Initialize Vercel Analytics only after opt-in. + +## Security Note (important) +A file like [`.env`](.env:1) must never be committed with real credentials. If secrets were shared outside your team, rotate/revoke them (WooCommerce keys and any WordPress app password), then regenerate. + +## Open Questions (to resolve during implementation) +- Confirm default locale (unprefixed) is `en` and confirm chosen secondary prefix is `/de/*`. +- Confirm which WP/WC REST endpoints are Polylang language-aware on this site. +- Detect any locale-specific slugs that collide across content types. + +## Next Step +Proceed to implement the export pipeline and data model with locale support, then scaffold Next.js templates and routing per locale. \ No newline at end of file