Skip to content

Commit 6796e54

Browse files
committed
fix: issue with locale rewrites on pages router
1 parent 335f73b commit 6796e54

3 files changed

Lines changed: 378 additions & 36 deletions

File tree

.changeset/fifty-kiwis-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@headstartwp/next": patch
3+
---
4+
5+
Fix issue with locale rewrites on pages router

packages/next/src/config/__tests__/withHeadstartWPConfig.ts

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,296 @@ describe('withHeadstartWPConfig - Host Check', () => {
353353
});
354354
});
355355
});
356+
357+
describe('Locale/i18n Handling', () => {
358+
beforeEach(() => {
359+
// Mock file system to ensure we're using pages router (not app router)
360+
// by returning false for app directory checks
361+
// eslint-disable-next-line global-require
362+
const fs = require('fs');
363+
const mockExistsSync = fs.existsSync;
364+
mockExistsSync.mockImplementation((path: string) => {
365+
if (path.includes('headstartwp.config.js') || path.includes('headless.config.js')) {
366+
return true;
367+
}
368+
// Return false for app directory to ensure pages router
369+
if (path.includes('/app') || path.includes('src/app')) {
370+
return false;
371+
}
372+
return false;
373+
});
374+
});
375+
376+
it('should create locale-aware rewrites when i18n is configured for pages router', async () => {
377+
const headlessConfig = {
378+
sourceUrl: 'https://wp.example.com',
379+
};
380+
381+
const nextConfig = withHeadstartWPConfig(
382+
{
383+
i18n: {
384+
locales: ['en', 'es', 'fr'],
385+
defaultLocale: 'en',
386+
},
387+
},
388+
headlessConfig,
389+
);
390+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
391+
392+
expect(Array.isArray(rewrites)).toBe(true);
393+
// Should have 7 paths * 4 variants (default + 3 locales) = 28 rewrites
394+
// Plus 1 sitemap xsl rewrite without locale + 3 with locales = 4
395+
// Total: 28 + 4 = 32
396+
expect(rewrites).toHaveLength(32);
397+
398+
// Check that we have rewrites for each locale
399+
const cacheHealthcheckRewrites = (rewrites as Rewrite[]).filter((r) =>
400+
r.source.includes('cache-healthcheck'),
401+
);
402+
expect(cacheHealthcheckRewrites).toHaveLength(4); // default + 3 locales
403+
404+
// Check default rewrite (no locale)
405+
const defaultRewrite = cacheHealthcheckRewrites.find(
406+
(r) => r.source === '/cache-healthcheck',
407+
);
408+
expect(defaultRewrite).toBeDefined();
409+
expect(defaultRewrite?.destination).toBe('/api/cache-healthcheck');
410+
411+
// Check locale-specific rewrites
412+
const enRewrite = cacheHealthcheckRewrites.find(
413+
(r) => r.source === '/en/cache-healthcheck',
414+
);
415+
expect(enRewrite).toBeDefined();
416+
expect(enRewrite?.destination).toBe('/api/cache-healthcheck');
417+
418+
const esRewrite = cacheHealthcheckRewrites.find(
419+
(r) => r.source === '/es/cache-healthcheck',
420+
);
421+
expect(esRewrite).toBeDefined();
422+
expect(esRewrite?.destination).toBe('/api/cache-healthcheck');
423+
424+
const frRewrite = cacheHealthcheckRewrites.find(
425+
(r) => r.source === '/fr/cache-healthcheck',
426+
);
427+
expect(frRewrite).toBeDefined();
428+
expect(frRewrite?.destination).toBe('/api/cache-healthcheck');
429+
});
430+
431+
it('should not create locale-aware rewrites when i18n is not configured', async () => {
432+
const headlessConfig = {
433+
sourceUrl: 'https://wp.example.com',
434+
};
435+
436+
const nextConfig = withHeadstartWPConfig({}, headlessConfig);
437+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
438+
439+
expect(Array.isArray(rewrites)).toBe(true);
440+
expect(rewrites).toHaveLength(8); // Standard 8 rewrites without locales
441+
442+
// Verify no locale prefixes in sources
443+
(rewrites as Rewrite[]).forEach((rewrite) => {
444+
expect(rewrite.source).not.toMatch(/^\/(en|es|fr)\//);
445+
});
446+
});
447+
448+
it('should create locale-aware rewrites for multisite with i18n', async () => {
449+
const headlessConfig = {
450+
sites: [
451+
{
452+
sourceUrl: 'https://wp.site1.com',
453+
host: 'site1.com',
454+
},
455+
{
456+
sourceUrl: 'https://wp.site2.com',
457+
host: 'site2.com',
458+
},
459+
],
460+
};
461+
462+
const nextConfig = withHeadstartWPConfig(
463+
{
464+
i18n: {
465+
locales: ['en', 'es'],
466+
defaultLocale: 'en',
467+
},
468+
},
469+
headlessConfig,
470+
);
471+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
472+
473+
expect(Array.isArray(rewrites)).toBe(true);
474+
// 2 sites * 7 paths * 3 variants (default + 2 locales) = 42
475+
// Plus 2 sites * 3 sitemap xsl rewrites (default + 2 locales) = 6
476+
// Total: 42 + 6 = 48
477+
expect(rewrites).toHaveLength(48);
478+
479+
// Check multisite prefix with locales
480+
const site1CacheRewrites = (rewrites as Rewrite[]).filter(
481+
(r) => r.source.includes('_sites/:site') && r.source.includes('cache-healthcheck'),
482+
);
483+
expect(site1CacheRewrites.length).toBeGreaterThan(0);
484+
485+
// Check that we have rewrites with multisite prefix and locales
486+
const multisiteEnRewrite = (rewrites as Rewrite[]).find(
487+
(r) => r.source === '/_sites/:site/en/cache-healthcheck',
488+
);
489+
expect(multisiteEnRewrite).toBeDefined();
490+
491+
const multisiteEsRewrite = (rewrites as Rewrite[]).find(
492+
(r) => r.source === '/_sites/:site/es/cache-healthcheck',
493+
);
494+
expect(multisiteEsRewrite).toBeDefined();
495+
496+
const multisiteDefaultRewrite = (rewrites as Rewrite[]).find(
497+
(r) => r.source === '/_sites/:site/cache-healthcheck',
498+
);
499+
expect(multisiteDefaultRewrite).toBeDefined();
500+
});
501+
502+
it('should handle sitemap stylesheet rewrites with locales', async () => {
503+
const headlessConfig = {
504+
sourceUrl: 'https://wp.example.com',
505+
};
506+
507+
const nextConfig = withHeadstartWPConfig(
508+
{
509+
i18n: {
510+
locales: ['en', 'es'],
511+
defaultLocale: 'en',
512+
},
513+
},
514+
headlessConfig,
515+
);
516+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
517+
518+
// Find sitemap xsl rewrites - the pattern uses escaped dots
519+
const sitemapXslRewrites = (rewrites as Rewrite[]).filter((r) =>
520+
r.source.includes('main-sitemap'),
521+
);
522+
523+
// Should have default + 2 locales = 3 rewrites
524+
expect(sitemapXslRewrites).toHaveLength(3);
525+
526+
// Check default - pattern uses \\. for escaped dot
527+
const defaultXsl = sitemapXslRewrites.find((r) =>
528+
r.source.includes(':path(.*main-sitemap'),
529+
);
530+
expect(defaultXsl).toBeDefined();
531+
expect(defaultXsl?.source).toBe('/:path(.*main-sitemap\\.xsl)');
532+
533+
// Check locale-specific
534+
const enXsl = sitemapXslRewrites.find((r) => r.source.startsWith('/en/'));
535+
expect(enXsl).toBeDefined();
536+
expect(enXsl?.source).toBe('/en/:path(.*main-sitemap\\.xsl)');
537+
538+
const esXsl = sitemapXslRewrites.find((r) => r.source.startsWith('/es/'));
539+
expect(esXsl).toBeDefined();
540+
expect(esXsl?.source).toBe('/es/:path(.*main-sitemap\\.xsl)');
541+
});
542+
543+
it('should not apply i18n rewrites when using app router', async () => {
544+
// Mock app router by returning true for app directory
545+
// eslint-disable-next-line global-require
546+
const fs = require('fs');
547+
const mockExistsSync = fs.existsSync;
548+
mockExistsSync.mockImplementation((path: string) => {
549+
if (path.includes('headstartwp.config.js') || path.includes('headless.config.js')) {
550+
return true;
551+
}
552+
// Return true for app directory to simulate app router
553+
if (path.includes('/app') || path.includes('src/app')) {
554+
return true;
555+
}
556+
return false;
557+
});
558+
559+
const headlessConfig = {
560+
sourceUrl: 'https://wp.example.com',
561+
};
562+
563+
const nextConfig = withHeadstartWPConfig(
564+
{
565+
i18n: {
566+
locales: ['en', 'es'],
567+
defaultLocale: 'en',
568+
},
569+
},
570+
headlessConfig,
571+
);
572+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
573+
574+
expect(Array.isArray(rewrites)).toBe(true);
575+
// App router should not use Next.js i18n, so should have standard 8 rewrites
576+
expect(rewrites).toHaveLength(8);
577+
578+
// Verify no locale prefixes (app router uses its own i18n system)
579+
(rewrites as Rewrite[]).forEach((rewrite) => {
580+
expect(rewrite.source).not.toMatch(/^\/(en|es)\//);
581+
});
582+
});
583+
584+
it('should handle feed rewrites with locales', async () => {
585+
const headlessConfig = {
586+
sourceUrl: 'https://wp.example.com',
587+
};
588+
589+
const nextConfig = withHeadstartWPConfig(
590+
{
591+
i18n: {
592+
locales: ['en', 'de'],
593+
defaultLocale: 'en',
594+
},
595+
},
596+
headlessConfig,
597+
);
598+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
599+
600+
// Find feed rewrites
601+
const feedRewrites = (rewrites as Rewrite[]).filter((r) => r.source.includes('feed'));
602+
603+
// Should have default + 2 locales = 3 rewrites
604+
expect(feedRewrites).toHaveLength(3);
605+
606+
// Check all have correct destination
607+
feedRewrites.forEach((rewrite) => {
608+
expect(rewrite.destination).toBe('https://wp.example.com/feed/?rewrite_urls=1');
609+
});
610+
611+
// Check sources
612+
const defaultFeed = feedRewrites.find((r) => r.source === '/feed');
613+
expect(defaultFeed).toBeDefined();
614+
615+
const enFeed = feedRewrites.find((r) => r.source === '/en/feed');
616+
expect(enFeed).toBeDefined();
617+
618+
const deFeed = feedRewrites.find((r) => r.source === '/de/feed');
619+
expect(deFeed).toBeDefined();
620+
});
621+
622+
it('should preserve host checks when i18n is configured', async () => {
623+
const headlessConfig = {
624+
sourceUrl: 'https://wp.example.com',
625+
host: 'example.com',
626+
};
627+
628+
const nextConfig = withHeadstartWPConfig(
629+
{
630+
i18n: {
631+
locales: ['en', 'es'],
632+
defaultLocale: 'en',
633+
},
634+
},
635+
headlessConfig,
636+
);
637+
const rewrites = (await nextConfig.rewrites?.()) ?? [];
638+
639+
// All rewrites should have host check
640+
(rewrites as Rewrite[]).forEach((rewrite) => {
641+
expect(rewrite).toHaveProperty('has');
642+
expect(rewrite.has).toEqual([
643+
{ type: 'header', key: 'host', value: 'example.com' },
644+
]);
645+
});
646+
});
647+
});
356648
});

0 commit comments

Comments
 (0)