7.5 KiB
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
ResendandTurnstile. - Add analytics using
Vercel Analyticswith 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:
/blogand/blog/{postSlug} - Products:
/product/{productSlug} - Product categories:
/product-category/{slug}
- Pages:
- 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
hreflangalternate 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,localeslug,pathtitleHtml,contentHtml,excerptHtml?featuredImage?(asset reference)updatedAt
Post
id,translationKey,localeslug,path(/blog/{slug}or/de/blog/{slug})titleHtml,contentHtml,excerptHtmlfeaturedImage?datePublished,updatedAt
Product (WooCommerce catalog-only)
id,translationKey,localeslug,path(/product/{slug}or/de/product/{slug})nameshortDescriptionHtml,descriptionHtmlimages[],featuredImage?sku?,regularPrice?,salePrice?,currency(global config allowed)stockStatus?categories[](references),attributes[](for filtering)variations[](if present)updatedAt
ProductCategory
id,translationKey,localeslug,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
/publicpath, 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=enand?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 towp-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}.jsondata/raw/{timestamp}/posts.{locale}.jsondata/raw/{timestamp}/products.{locale}.jsondata/raw/{timestamp}/product-categories.{locale}.jsondata/raw/{timestamp}/menus.{locale}.jsondata/raw/{timestamp}/media.json+ mirrored files under/public/media/...
Commands / Techniques
- Use
wp option getand 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 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
enand 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.