import createNextIntlPlugin from 'next-intl/plugin'; import { createMDX } from 'fumadocs-mdx/next'; const withNextIntl = createNextIntlPlugin({ experimental: { srcPath: './src', // Enables `useExtracted` (compile-time message extraction). Required for the // production build — without it, `useExtracted` throws at runtime. The // source locale now lives in `messages.sourceLocale` below. extract: true, messages: { sourceLocale: 'en', path: './messages', format: 'json', locales: ['en', 'de', 'fr', 'zh', 'es', 'pl', 'it', 'ko', 'pt', 'ja'], }, }, }); const withMDX = createMDX(); // Vercel packages monorepo builds relative to its supplied tracing root. // Keep local builds rooted in docs when that override is absent. const tracingRoot = process.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT || import.meta.dirname; /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, outputFileTracingRoot: tracingRoot, turbopack: { root: tracingRoot }, images: { remotePatterns: [ { protocol: 'https', hostname: 'pbs.twimg.com', }, { protocol: 'https', hostname: 'abs.twimg.com', }, { protocol: 'https', hostname: 'ui-avatars.com', }, { protocol: 'https', hostname: 'www.google.com', }, ], }, async redirects() { // Tools retired 2026-07-18 (off-brand / no organic traffic). Redirect the old URLs to the // tools index so residual links and SERP clicks land somewhere useful instead of a 404. const logoPlatforms = [ 'instagram', 'x', 'tiktok', 'linkedin', 'youtube', 'facebook', 'pinterest', 'discord', 'twitch', 'snapchat', 'reddit', 'threads', 'bluesky', 'mastodon', 'github', 'medium', 'substack', 'spotify', 'telegram', 'whatsapp', ]; const hashtagPlatforms = ['instagram', 'tiktok', 'x', 'linkedin', 'facebook', 'youtube', 'pinterest', 'threads', 'tumblr']; const retiredSlugs = [ ...logoPlatforms.map(p => `${p}-logo-generator`), ...hashtagPlatforms.map(p => `${p}-hashtag-generator`), ...['nostr', 'lemmy', 'warpcast', 'dribbble', 'mastodon'].map(p => `${p}-font-generator`), ...['nostr', 'lemmy', 'warpcast', 'dribbble', 'mastodon', 'vk'].map(p => `${p}-comment-generator`), 'medium-page-name-generator', 'medium-post-generator', 'medium-username-generator', 'mastodon-character-counter', ...['mastodon', 'medium'].map(p => `${p}-bio-generator`), ...['nostr', 'lemmy', 'warpcast', 'dribbble', 'mastodon', 'medium', 'vk', 'tumblr'].map(p => `${p}-photo-resizer`), ]; // URLs that no longer exist but are still linked from old blog posts, external // sites and the search index. Specific rules must precede the catch-alls. const legacyRedirects = [ // Docs reorganisation: behavior-analytics/* and product-analytics/* moved. ['/docs/behavior-analytics/:path*', '/docs/feature-guides/:path*'], ['/docs/product-analytics/funnels_guide', '/docs/funnels'], ['/docs/product-analytics/journeys_dashboard', '/docs/feature-guides/journeys'], ['/docs/product-analytics/retention_guide', '/docs/feature-guides/retention'], ['/docs/product-analytics/:path*', '/docs'], ['/docs/web-analytics/goals-tab', '/docs/goals'], ['/docs/web-analytics/:path*', '/docs/feature-guides/main-tab'], ['/docs/getting-started/:path*', '/docs'], ['/docs/blog/:path*', '/blog/:path*'], // Blog posts deleted before redirects existed; they still get visits and backlinks. ['/blog/data-privacy-2025', '/security'], ['/blog/master-digital-marketing', '/blog'], ['/blog/boost-growth-with-key-user-retention-metrics', '/features/retention'], ['/blog/replay-session', '/features/session-replay'], ['/blog/real-time-reports', '/features/web-analytics'], ['/blog/cross-site-tracking', '/features/web-analytics'], ['/blog/user-flow-examples-2025', '/features/user-journeys'], // 2026-09 blog cleanup: two posts merged into keyword-targeted guides, two removed. ['/blog/churn-rate-vs-retention-rate', '/blog/retention-analysis'], ['/blog/what-is-sales-funnel-rybbit', '/blog/funnel-analysis'], ['/blog/content-marketing-funnel', '/blog/funnel-analysis'], ['/blog/onyx-ai-knowledge-platform', '/blog'], ]; return [ ...retiredSlugs.map(slug => ({ source: `/tools/${slug}`, destination: '/tools', permanent: true, })), ...legacyRedirects.map(([source, destination]) => ({ source, destination, permanent: true })), ]; }, async rewrites() { return [ { source: '/docs/:path*.mdx', destination: '/llms.mdx/:path*', }, ]; }, }; export default withNextIntl(withMDX(config));