Files
klz-cables.com/plans/wordpress-to-nextjs-concept.md
2025-12-27 22:17:35 +01:00

7.5 KiB
Raw Blame History

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 and Turnstile.
  • Add analytics using Vercel Analytics with GDPR opt-in consent.

Key Constraints & Decisions

  • Access: SSH/SFTP, server DB credentials, and ability to run wp-cli.
  • 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 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 doesnt 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 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.