- Update sitemap.ts to use page.localizedSlugs[locale] for correct URLs
- Generate all 40 localized URLs (10 pages × 4 locales) with proper slugs
- Remove outdated sitemap-programmatic.xml
- Add proper TypeScript type for solutionPages
- Update [slug]/page.tsx to always include locale in canonical URL
- Update by-oil/page.tsx to use consistent locale prefix
- Update by-concern/page.tsx to use consistent locale prefix
- All canonical URLs now match actual URLs: /{locale}/solutions/{slug}
- Update basePath to always include locale prefix (/)
- Fix productsHref to use basePath consistently
- Fix product links in ProductsGrid to use basePath
- Ensure /sr/pages link to /sr/products, /en to /en/products, etc.
- Add 10 oil-for-concern solution pages with localized slugs
- Support 4 languages: sr, en, de, fr with proper canonical URLs
- Add solutions hub, by-concern, and by-oil directory pages
- Filter bundle products from solutions pages
- Add hideLangSwitcher prop to Header component
- Update translations for all languages
- Fix canonical URLs to include locale prefix
- Remove kubectl command that was causing build failures
- Flux will auto-detect new image within 5 minutes via polling
- Simpler, more reliable build process
- Use 'command' and 'args' instead of multiline command
- Use quoted heredoc delimiter to prevent variable expansion
- Simplify clone and build scripts
- Add ARG and ENV directives to Dockerfile for NEXT_PUBLIC_* vars
- Pass build args in buildctl command with --opt build-arg
- Fixes ERR_BLOCKED_BY_CLIENT on localhost:8000/graphql
Add .gitea/workflows/build.yaml that:
- Builds Docker image on push to master
- Pushes to ghcr.io/unchainedio/manoon-headless
- Tags with commit SHA and 'latest'
- Updates k8s/kustomization.yaml with new image tag
- Commits and pushes the tag update back to repo
Requires Gitea Actions runner to be configured.
- Remove init containers (clone, install, build)
- Use ghcr.io/unchainedio/manoon-headless:latest image
- Faster pod startup, less resource usage
- Image built by GitHub Actions on push to master
Apply same fix from master branch:
- Check if workspace exists before cloning
- Fetch and reset if .git directory exists
- Clean and clone fresh if not
Prevents CrashLoopBackOff on pod restarts.
The clone init container was failing with 'destination path already exists'
when the pod restarted. EmptyDir volumes persist across container restarts
but init containers run again.
Now checks if workspace exists:
- If .git directory exists: fetch and reset to latest master
- If not: clean and clone fresh
This fixes the CrashLoopBackOff caused by failed clone attempts.
- Email capture popup with scroll (10%) and exit intent triggers
- First name field and full tracking (UTM, device, time on page)
- Mautic API integration for contact creation
- GeoIP detection for country/region
- 4 locale support (sr, en, de, fr)
- Mautic tracking script in layout
Changed Rybbit script loading from server-side rewrite to client-side direct
connection. This prevents Next.js from proxying the request, which was causing
all visitor IPs to show as the Hetzner server IP (138.201.11.251).
Before:
- Browser → Next.js → Rybbit (server-side proxy, loses client IP)
After:
- Browser → Rybbit (direct connection, real IP preserved)
Changes:
- layout.tsx: Use direct Rybbit URL for script src
- next.config.ts: Remove /api/script.js rewrite
Change storefront service from ClusterIP to NodePort with externalTrafficPolicy: Local.
This preserves the real client source IP instead of NATing to the node IP.
Fixes analytics tracking showing Hetzner IP (138.201.11.251) instead of real visitor IPs.
Same fix previously applied to Rybbit backend service.
Note: On single-node clusters, this works seamlessly. Traefik routes directly
to the node where the pod is running, preserving the original source IP.
- Add permanent redirects for /products/manoon to /products
- Strip malformed /contact suffix from product URLs
- Create custom branded 404 page with product navigation
- Add NotFound translations for en and sr locales
- Create new API route /api/rybbit/track to proxy Rybbit tracking requests
- Extract real client IP from Cloudflare headers (cf-connecting-ip)
- Forward X-Forwarded-For and X-Real-IP headers to analytics backends
- Update OpenPanel proxy to also forward client IP
- Update next.config.ts rewrite to use internal API route
This fixes geo-location issues where all traffic appeared to come from
Cloudflare edge locations instead of actual visitor countries.
- Create new API route /api/rybbit/track to proxy Rybbit tracking requests
- Extract real client IP from Cloudflare headers (cf-connecting-ip)
- Forward X-Forwarded-For and X-Real-IP headers to analytics backends
- Update OpenPanel proxy to also forward client IP
- Update next.config.ts rewrite to use internal API route
This fixes geo-location issues where all traffic appeared to come from
Cloudflare edge locations instead of actual visitor countries.
- Add revalidate=3600 to homepage and products page (1hr ISR)
- Add middleware to set cache headers for HTML pages
- Bypass cache for checkout and cart pages
- Add type-safe AnalyticsEvent union types
- Create AnalyticsProvider interface for pluggable analytics backends
- Implement OpenPanelProvider and RybbitProvider adapters
- Create AnalyticsTracker that fans out events to all providers
- Simplifies adding new analytics platforms in the future
- Add Rybbit server-side tracking to analytics-server.ts for order completion and revenue
- Add trackNewsletterSignup to analytics.ts and wire up NewsletterSection
- Add cart tracking to CartDrawer (cart view, remove from cart)
- All ecommerce events now track to both OpenPanel and Rybbit
- Add Next.js rewrites to proxy /api/script.js and /api/track through self-hosted Rybbit
- This bypasses ad blockers that would block rybbit.nodecrew.me directly
- Add NEXT_PUBLIC_RYBBIT_HOST and NEXT_PUBLIC_RYBBIT_SITE_ID env vars to K8s deployment
- Add RybbitService for tracking e-commerce events
- Update useAnalytics hook to track with both OpenPanel and Rybbit
- Add Rybbit script to layout for page view tracking
- Track all applicable store events: product views, cart, checkout, orders, search, etc.
- Remove dev.manoonoils.com from storefront ingress to prevent cross-domain tracking issues
- Use environment variable for OpenPanel API URL in route handler
- Fixes session state conflicts from multiple domains
- Updated monitor.py to support both OAuth and Service Account
- Created setup-oauth-local.py for easy local authorization
- Created cronjob-oauth.yaml for OAuth-based deployment
- Updated README with both authentication options
- OAuth is now the recommended method (no key file needed)
- Python monitoring script for daily GSC reports
- Kubernetes CronJob for automated execution
- Tracks search analytics, crawl errors, and sitemap status
- Includes full setup documentation
- Update ingress to serve all domains (dev.manoonoils.com, manoonoils.com, www.manoonoils.com)
- Update NEXT_PUBLIC_SITE_URL to https://manoonoils.com in deployment env vars
- Prepare for 24h testing period before removing dev domain
- Add template replacement logic for product keywords
- Replace {{productName}} with actual product.name
- Keywords now show correct product name instead of template variable
- Add keywords, canonical, OpenGraph to About page
- Refactor Contact page to server component with generateMetadata
- Create ContactPageClient for form functionality
- All pages now have complete SEO coverage
- Document actual rendered HTML structure
- Show extracted JSON-LD schemas
- Include complete verification test results
- Prove all 7/7 SEO checks pass with real data
- Remove next/script dependency causing SSR issues
- Use regular script tag for server-side rendering
- Add real SEO verification test that checks rendered output
- All 7/7 SEO checks now passing
- Add comprehensive SEO implementation guide
- Add automated SEO testing script
- Document all schema types and integrations
- Include verification methods and expected impact
- Fix shipping cost not included in checkout total
- Add useShippingMethodSelector hook for proper abstraction
- Remove blocking initCheckout from Header for better performance
- Checkout now initializes lazily when cart opens or item added
Complete analytics overhaul with redundant tracking:
CLIENT-SIDE (useAnalytics hook):
- Tracks user behavior in real-time
- Product views, add to cart, checkout steps
- Revenue tracking via op.revenue()
- Captures user session data
SERVER-SIDE (API route + server functions):
- POST /api/analytics/track-order endpoint
- trackOrderCompletedServer() function
- Reliable tracking that can't be blocked
- Works even if browser closes
DUAL TRACKING for order completion:
1. Client tracks immediately (session data)
2. API call to server endpoint (reliable)
3. Both sources recorded with 'source' property
Files:
- src/lib/analytics.ts - Client-side with dual tracking
- src/lib/analytics-server.ts - Server-side tracking
- src/app/api/analytics/track-order/route.ts - API endpoint
Benefits:
- ✅ 100% revenue capture (server-side backup)
- ✅ Real-time client tracking
- ✅ Ad blocker resistant
- ✅ Browser-close resistant
- ✅ Full funnel visibility
The checkout was being cleared before tracking, causing getTotal()
to return 0. Fixed by reordering operations:
1. Track order completion (while checkout data exists)
2. Then clear the checkout
Added console log to verify total is captured correctly.
Add detailed console logs to debug why revenue tracking isn't working:
- Log when trackOrderCompleted is called
- Log revenue amount and currency
- Log success/failure of revenue tracking
This will help identify if the issue is with the op.revenue() call
or if it's failing silently.
The order confirmation requires MANAGE_ORDERS permission which
the storefront API token doesn't have. Removing the auto-confirmation
attempt to prevent console errors. Orders will remain UNCONFIRMED
until manually confirmed in Saleor Dashboard.
Remove complex tracking implementation that was causing errors:
- Remove op.increment/decrement calls (causing Duplicate event errors)
- Remove complex type definitions
- Remove unused tracking methods
- Keep only essential tracking with proper error handling
This reverts to a simpler, working analytics implementation.