From f2758abc85335aa6d62763bd3694fb9bf73db548 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Fri, 6 Feb 2026 22:35:49 +0100 Subject: [PATCH] refactor: Standardize Umami analytics environment variables to non-public names with fallbacks to `NEXT_PUBLIC_` prefixed versions. --- .env | 4 +- .env.example | 4 +- .env.production | 4 +- .gitea/workflows/deploy.yml | 16 +- Dockerfile | 8 +- README.md | 55 ++++- app/[locale]/layout.tsx | 3 +- components/analytics/AnalyticsProvider.tsx | 35 +--- docs/DEPLOYMENT.md | 75 ++++--- docs/ENV_MIGRATION.md | 61 +++--- lib/config.ts | 10 +- lib/env.ts | 13 +- .../analytics/umami-analytics-service.ts | 196 +++++++----------- lib/services/create-services.ts | 2 +- next.config.mjs | 2 +- 15 files changed, 243 insertions(+), 245 deletions(-) diff --git a/.env b/.env index 7523ee9f..ab749cb0 100644 --- a/.env +++ b/.env @@ -1,8 +1,8 @@ # Application NODE_ENV=production NEXT_PUBLIC_BASE_URL=https://klz-cables.com -UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js -NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3 +UMAMI_API_ENDPOINT=https://analytics.infra.mintel.me +UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3 SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1 LOG_LEVEL=info diff --git a/.env.example b/.env.example index f2fa2702..64642189 100644 --- a/.env.example +++ b/.env.example @@ -20,8 +20,8 @@ TARGET=development # Analytics (Umami) # ──────────────────────────────────────────────────────────────────────────── # Optional: Leave empty to disable analytics -NEXT_PUBLIC_UMAMI_WEBSITE_ID= -NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js +UMAMI_WEBSITE_ID= +UMAMI_API_ENDPOINT=https://analytics.infra.mintel.me # ──────────────────────────────────────────────────────────────────────────── # Error Tracking (GlitchTip/Sentry) diff --git a/.env.production b/.env.production index 0d41ad03..8e779c14 100644 --- a/.env.production +++ b/.env.production @@ -12,8 +12,8 @@ NODE_ENV=production NEXT_PUBLIC_BASE_URL=https://klz-cables.com # Analytics (Umami) -NEXT_PUBLIC_UMAMI_WEBSITE_ID= -NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js +UMAMI_WEBSITE_ID= +UMAMI_API_ENDPOINT=https://analytics.infra.mintel.me # Error Tracking (GlitchTip/Sentry) SENTRY_DSN= diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 7f379d8f..4e756abb 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -198,16 +198,16 @@ jobs: IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }} TARGET: ${{ needs.prepare.outputs.target }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_base_url }} - NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} - NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} + UMAMI_WEBSITE_ID: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} + UMAMI_API_ENDPOINT: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }} run: | docker buildx build \ --pull \ --platform linux/arm64 \ --build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \ - --build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \ - --build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="$NEXT_PUBLIC_UMAMI_SCRIPT_URL" \ + --build-arg UMAMI_WEBSITE_ID="$UMAMI_WEBSITE_ID" \ + --build-arg UMAMI_API_ENDPOINT="$UMAMI_API_ENDPOINT" \ --build-arg NEXT_PUBLIC_TARGET="$TARGET" \ --build-arg DIRECTUS_URL="$DIRECTUS_URL" \ -t registry.infra.mintel.me/mintel/klz-cables.com:$IMAGE_TAG \ @@ -231,8 +231,8 @@ jobs: ENV_FILE: ${{ needs.prepare.outputs.env_file }} TRAEFIK_HOST: ${{ needs.prepare.outputs.traefik_host }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_base_url }} - NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} - NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} + UMAMI_WEBSITE_ID: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} + UMAMI_API_ENDPOINT: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} SENTRY_DSN: ${{ secrets.SENTRY_DSN || vars.SENTRY_DSN || (needs.prepare.outputs.target == 'production' && secrets.SENTRY_DSN || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_SENTRY_DSN || secrets.TESTING_SENTRY_DSN || secrets.SENTRY_DSN)) }} MAIL_HOST: ${{ secrets.MAIL_HOST || vars.MAIL_HOST || (needs.prepare.outputs.target == 'production' && (secrets.MAIL_HOST || vars.MAIL_HOST) || (needs.prepare.outputs.target == 'staging' && (secrets.STAGING_MAIL_HOST || vars.STAGING_MAIL_HOST) || (secrets.TESTING_MAIL_HOST || vars.TESTING_MAIL_HOST) || (secrets.MAIL_HOST || vars.MAIL_HOST))) }} MAIL_PORT: ${{ secrets.MAIL_PORT || vars.MAIL_PORT || (needs.prepare.outputs.target == 'production' && (secrets.MAIL_PORT || vars.MAIL_PORT) || (needs.prepare.outputs.target == 'staging' && (secrets.STAGING_MAIL_PORT || vars.STAGING_MAIL_PORT) || (secrets.TESTING_MAIL_PORT || vars.TESTING_MAIL_PORT) || (secrets.MAIL_PORT || vars.MAIL_PORT))) }} @@ -273,8 +273,8 @@ jobs: NODE_ENV=production NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_TARGET=$TARGET - NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID - NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL + UMAMI_WEBSITE_ID=$UMAMI_WEBSITE_ID + UMAMI_API_ENDPOINT=$UMAMI_API_ENDPOINT SENTRY_DSN=$SENTRY_DSN LOG_LEVEL=$( [[ "$TARGET" == "testing" || "$TARGET" == "development" ]] && echo "debug" || echo "info" ) MAIL_HOST=$MAIL_HOST diff --git a/Dockerfile b/Dockerfile index 9a1ede8a..34f68483 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,14 +25,18 @@ ENV NEXT_TELEMETRY_DISABLED=1 # Build-time environment variables for Next.js # These are baked into the client bundle during build ARG NEXT_PUBLIC_BASE_URL +ARG NEXT_PUBLIC_BASE_URL +ARG UMAMI_WEBSITE_ID ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID +ARG UMAMI_API_ENDPOINT +ARG UMAMI_SCRIPT_URL ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL ARG NEXT_PUBLIC_TARGET ARG DIRECTUS_URL ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL -ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID -ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL +ENV UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID:-$NEXT_PUBLIC_UMAMI_WEBSITE_ID} +ENV UMAMI_API_ENDPOINT=${UMAMI_API_ENDPOINT:-${UMAMI_SCRIPT_URL:-$NEXT_PUBLIC_UMAMI_SCRIPT_URL}} ENV NEXT_PUBLIC_TARGET=$NEXT_PUBLIC_TARGET ENV DIRECTUS_URL=$DIRECTUS_URL diff --git a/README.md b/README.md index 093e10ba..f09b12fa 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,13 @@ A complete WordPress to Next.js static site migration for KLZ Cables, transformi ## 🚀 Quick Start ### Prerequisites -- Node.js 18+ + +- Node.js 18+ - npm or yarn ### Installation -```bash + +````bash # Install dependencies npm install --legacy-peer-deps @@ -42,11 +44,12 @@ npm run cms:logs # Stop the CMS npm run cms:stop -``` +```` Once running, you can access the Strapi admin panel at `http://localhost:1337/admin`. ### 🔄 Data & Migration + To sync data or migrate existing content: ```bash @@ -61,6 +64,7 @@ npm run cms:migrate ``` ### Environment Variables + ```bash # .env SITE_URL=https://klz-cables.com @@ -69,8 +73,8 @@ TURNSTILE_SITE_KEY=your_turnstile_key TURNSTILE_SECRET_KEY=your_turnstile_secret # Umami -NEXT_PUBLIC_UMAMI_WEBSITE_ID=your_umami_website_id -NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js +UMAMI_WEBSITE_ID=your_umami_website_id +UMAMI_API_ENDPOINT=https://analytics.infra.mintel.me # GlitchTip (Sentry compatible) SENTRY_DSN=https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID @@ -81,6 +85,7 @@ NEXT_PUBLIC_SENTRY_DSN=https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID ## 📊 Project Overview ### Migration Statistics + - **Content Exported**: 141 items - 18 pages (9 EN + 9 DE) - 59 posts (29 EN + 30 DE) @@ -91,6 +96,7 @@ NEXT_PUBLIC_SENTRY_DSN=https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID - **Translation Pairs**: 16 ### Performance Benefits + - **Before**: Dynamic WordPress with database queries - **After**: Static HTML with CDN delivery - **Load Time**: <100ms (vs 500ms+) @@ -99,6 +105,7 @@ NEXT_PUBLIC_SENTRY_DSN=https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID ## 🏗️ Architecture ### Tech Stack + - **Framework**: Next.js 14 (App Router) - **Language**: TypeScript - **Styling**: SCSS @@ -109,6 +116,7 @@ NEXT_PUBLIC_SENTRY_DSN=https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID - **CAPTCHA**: Cloudflare Turnstile ### Project Structure + ``` app/ ├── layout.tsx # Root layout @@ -163,6 +171,7 @@ scripts/ ## 🎯 Features ### ✅ Implemented + - **Multi-language**: EN/DE with `/de/` prefix routing - **Contact Forms**: Resend integration with validation - **GDPR Compliance**: Cookie consent banner @@ -175,12 +184,14 @@ scripts/ - **Asset Management**: WordPress → local path mapping ### 🔄 In Progress + - Analytics integration (consent-based) - Turnstile CAPTCHA - Build testing - Deployment configuration ### 📝 Remaining + - Performance optimization - Final QA testing - Documentation updates @@ -188,6 +199,7 @@ scripts/ ## 📝 Content Management ### Data Export + ```bash # Export from WordPress npm run data:export @@ -203,6 +215,7 @@ npm run data:improve-mapping ``` ### Adding New Content + 1. Export new content from WordPress 2. Process the data 3. Rebuild the site @@ -210,17 +223,20 @@ npm run data:improve-mapping ## 🎨 Design System ### Colors + - Primary: `#0066cc` (KLZ Blue) - Secondary: `#00a896` (Teal) - Text: `#1a1a1a` - Background: `#f8f9fa` ### Typography + - Font: Inter - Base: 16px - Scale: 1.25 (Major Third) ### Layout + - Max width: 1200px - Responsive grid - Mobile-first @@ -228,6 +244,7 @@ npm run data:improve-mapping ## 🔧 API Endpoints ### Contact Form + ``` POST /api/contact { @@ -239,11 +256,13 @@ POST /api/contact ``` ### Sitemap + ``` GET /sitemap.xml ``` ### Robots + ``` GET /robots.txt ``` @@ -261,6 +280,7 @@ The project uses **Gitea Actions** for CI/CD. Every push to `main` or `staging` **Workflow**: `.gitea/workflows/deploy.yml` **Branch Deployments**: + - `main` branch: Deploys to production using `.env.prod` - `staging` branch: Deploys to staging using `.env.staging` @@ -268,12 +288,13 @@ The project uses **Gitea Actions** for CI/CD. Every push to `main` or `staging` The CI/CD workflow supports `STAGING_`-prefixed secrets (e.g., `STAGING_NEXT_PUBLIC_BASE_URL`) to override default secrets when deploying the `staging` branch. **Required Secrets** (configure in Gitea repository settings): + - `REGISTRY_USER` - Docker registry username - `REGISTRY_PASS` - Docker registry password - `ALPHA_SSH_KEY` - SSH private key for deployment - `NEXT_PUBLIC_BASE_URL` - Application base URL (e.g., `https://klz-cables.com`) -- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - Analytics ID -- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` - Analytics script URL +- `UMAMI_WEBSITE_ID` - Analytics ID +- `UMAMI_API_ENDPOINT` - Analytics API endpoint (formerly NEXT_PUBLIC_UMAMI_SCRIPT_URL) - `SENTRY_DSN` - Error tracking DSN - `MAIL_HOST`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD`, `MAIL_FROM`, `MAIL_RECIPIENTS` - Email configuration @@ -293,6 +314,7 @@ docker image prune -f ``` Or use the convenience script: + ```bash bash scripts/deploy-webhook.sh ``` @@ -304,11 +326,13 @@ Client → Traefik (TLS) → Next.js App ``` **Domains**: + - `klz-cables.com` - Production - `www.klz-cables.com` - Production (www) - `staging.klz-cables.com` - Staging **Services**: + - `app`: Next.js application (port 3000) - `traefik`: Reverse proxy (external) @@ -317,25 +341,30 @@ For detailed deployment documentation, see [`docs/DEPLOYMENT.md`](docs/DEPLOYMEN ## 📈 Performance ### Build Time + - **Target**: < 2 minutes - **Current**: ~1-2 minutes ### Page Load + - **Target**: < 100ms - **Current**: Static HTML from CDN ### Bundle Size + - **Target**: < 100KB gzipped - **Current**: Optimized with code splitting ## 🔒 Security ### Environment Variables + - Never commit `.env` file - Rotate keys regularly - Use secrets in deployment platform ### Form Security + - Email validation - Rate limiting (recommended) - Turnstile CAPTCHA (pending) @@ -343,6 +372,7 @@ For detailed deployment documentation, see [`docs/DEPLOYMENT.md`](docs/DEPLOYMEN ## 🎓 WordPress Specifics ### WPBakery Shortcodes Removed + - `[vc_row]`, `[vc_column]`, `[vc_column_text]` - `[nectar_*]` (Salient theme) - `[image_with_animation]` @@ -350,13 +380,16 @@ For detailed deployment documentation, see [`docs/DEPLOYMENT.md`](docs/DEPLOYMEN - `[divider]` ### HTML Sanitization + - Removes inline event handlers - Strips scripts - Normalizes classes - Preserves structure ### Asset Mapping + WordPress URLs → Local paths: + ``` https://klz-cables.com/wp-content/uploads/... → /media/... ``` @@ -364,11 +397,13 @@ https://klz-cables.com/wp-content/uploads/... → /media/... ## 📚 Documentation ### Internal + - `PROJECT_STRUCTURE.md` - Detailed structure - `IMPLEMENTATION_SUMMARY.md` - Progress tracking - `FINAL_SUMMARY.md` - Complete overview ### External + - [Next.js Docs](https://nextjs.org/docs) - [WordPress REST API](https://developer.wordpress.org/rest-api/) - [Resend Docs](https://resend.com/docs) @@ -379,17 +414,20 @@ https://klz-cables.com/wp-content/uploads/... → /media/... ### Common Issues **TypeScript Errors** + - The TypeScript errors shown in the editor are expected - They occur because modules reference each other - The build process resolves these correctly - Run `npm run build` to verify **Build Failures** + - Check environment variables - Verify data files exist - Clear `.next` cache: `rm -rf .next` **Missing Modules** + - Run `npm install --legacy-peer-deps` - Check `package.json` dependencies @@ -404,11 +442,12 @@ https://klz-cables.com/wp-content/uploads/... → /media/... ✅ **i18n**: Multi-language support ✅ **SEO**: Metadata and sitemaps ✅ **Compatibility**: WPBakery content handled -✅ **Media**: All images downloaded +✅ **Media**: All images downloaded ## 📞 Support For issues or questions: + 1. Check the documentation 2. Review the troubleshooting section 3. Check environment variables diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index fff060f6..257f8c32 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -8,6 +8,7 @@ import { NextIntlClientProvider } from 'next-intl'; import { getMessages } from 'next-intl/server'; import '../../styles/globals.css'; import { SITE_URL } from '@/lib/schema'; +import { config } from '@/lib/config'; export const metadata: Metadata = { metadataBase: new URL(SITE_URL), @@ -51,7 +52,7 @@ export default async function LocaleLayout({ {/* Sends pageviews for client-side navigations */} - + diff --git a/components/analytics/AnalyticsProvider.tsx b/components/analytics/AnalyticsProvider.tsx index 30feb149..27b9ffb9 100644 --- a/components/analytics/AnalyticsProvider.tsx +++ b/components/analytics/AnalyticsProvider.tsx @@ -3,7 +3,6 @@ import { useEffect } from 'react'; import { usePathname, useSearchParams } from 'next/navigation'; import { getAppServices } from '@/lib/services/create-services'; -import Script from 'next/script'; /** * AnalyticsProvider Component @@ -11,49 +10,35 @@ import Script from 'next/script'; * Automatically tracks pageviews on client-side route changes. * This component should be placed inside your layout to handle navigation events. * + * @param {Object} props - Component props + * @param {string} [props.websiteId] - The Umami website ID (passed from server config) + * * @example * ```tsx * // In your layout.tsx - * - * - *
- *
{children}
- *