diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0635486090..e4ffae839f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Our versioning strategy is as follows:
### 🐛 Bug Fixes
+* `[sitecore-jss-nextjs]` Fix Link component locale handling to support both `languageEmbedding="always"` and `languageEmbedding="asNeeded"` Sitecore configurations. The component now auto-detects if the href already contains a locale prefix, preventing both double-prefixing and missing locale issues.
* `[sitecore-jss-nextjs]` Preserve default locale in external absolute urls ([#2142](https://github.com/Sitecore/jss/pull/2142))
* `[React]` Custom properties are not applied to empty field in editing metadata mode ([#2141](https://github.com/Sitecore/jss/pull/2141))
* `[sitecore-jss-nextjs]` Add regex variable substitution for absolute and external URL redirects. ([#2159](https://github.com/Sitecore/jss/pull/2159))
diff --git a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx
index ab390e7ef5..ffd0e4d331 100644
--- a/packages/sitecore-jss-nextjs/src/components/Link.test.tsx
+++ b/packages/sitecore-jss-nextjs/src/components/Link.test.tsx
@@ -7,7 +7,7 @@ import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtim
import { Link } from './Link';
import { spy } from 'sinon';
-const Router = (): NextRouter => ({
+const Router = (locales?: string[]): NextRouter => ({
pathname: '/',
route: '/',
query: {},
@@ -17,6 +17,7 @@ const Router = (): NextRouter => ({
isFallback: false,
isPreview: false,
isReady: false,
+ locales: locales,
events: { emit: spy(), off: spy(), on: spy() },
push: spy(() => Promise.resolve(true)),
replace: spy(() => Promise.resolve(true)),
@@ -27,8 +28,8 @@ const Router = (): NextRouter => ({
});
// Should provide RouterContext in case if we render Link from next/link
-const Page = ({ children }: { children: ReactNode }) => (
- {children}
+const Page = ({ children, locales }: { children: ReactNode; locales?: string[] }) => (
+ {children}
);
describe('', () => {
@@ -616,4 +617,88 @@ describe('', () => {
expect(rendered.container.innerHTML).to.equal('');
});
});
+
+ describe('locale handling', () => {
+ it('should not double-prefix locale when href already contains locale', () => {
+ const field = {
+ value: {
+ href: '/sv/about',
+ text: 'About',
+ },
+ };
+
+ const rendered = render(
+
+
+
+ );
+
+ const link = rendered.container.querySelector('a');
+
+ // Href should remain unchanged (no double prefix)
+ expect(link?.getAttribute('href')).to.equal('/sv/about');
+ expect(link?.getAttribute('data-nextjs-link')).to.equal('true');
+ });
+
+ it('should allow Next.js to add locale when href does not contain locale', () => {
+ const field = {
+ value: {
+ href: '/about',
+ text: 'About',
+ },
+ };
+
+ const rendered = render(
+
+
+
+ );
+
+ const link = rendered.container.querySelector('a');
+
+ // Link should render with NextLink (locale handling enabled)
+ expect(link?.getAttribute('data-nextjs-link')).to.equal('true');
+ });
+
+ it('should handle locale-only href paths', () => {
+ const field = {
+ value: {
+ href: '/sv',
+ text: 'Swedish Home',
+ },
+ };
+
+ const rendered = render(
+
+
+
+ );
+
+ const link = rendered.container.querySelector('a');
+
+ // Href should remain unchanged
+ expect(link?.getAttribute('href')).to.equal('/sv');
+ expect(link?.getAttribute('data-nextjs-link')).to.equal('true');
+ });
+
+ it('should work when no locales are configured', () => {
+ const field = {
+ value: {
+ href: '/about',
+ text: 'About',
+ },
+ };
+
+ const rendered = render(
+
+
+
+ );
+
+ const link = rendered.container.querySelector('a');
+
+ expect(link?.getAttribute('href')).to.equal('/about');
+ expect(link?.getAttribute('data-nextjs-link')).to.equal('true');
+ });
+ });
});
diff --git a/packages/sitecore-jss-nextjs/src/components/Link.tsx b/packages/sitecore-jss-nextjs/src/components/Link.tsx
index 5383b716a7..3112f416e6 100644
--- a/packages/sitecore-jss-nextjs/src/components/Link.tsx
+++ b/packages/sitecore-jss-nextjs/src/components/Link.tsx
@@ -1,6 +1,7 @@
import React, { forwardRef, JSX } from 'react';
import NextLink from 'next/link';
import { LinkProps as NextLinkProps } from 'next/link';
+import { useRouter } from 'next/router';
import {
Link as ReactLink,
LinkFieldValue,
@@ -37,6 +38,9 @@ export const Link = forwardRef(
...htmlLinkProps
} = props;
+ // Get configured locales from Next.js router for locale detection
+ const router = useRouter();
+
if (
!field ||
(!(field as LinkFieldValue).editable &&
@@ -65,11 +69,19 @@ export const Link = forwardRef(
// determine if a link is a route or not. File extensions are not routes and should not be pre-fetched.
if (isMatching && !isFileUrl) {
delete htmlLinkProps.emptyFieldEditingComponent;
+
+ // Check if href already contains a locale prefix to avoid double-prefixing.
+ // This supports both languageEmbedding="always" (locale in URL from Sitecore) and
+ // languageEmbedding="asNeeded" (locale may not be in URL, let Next.js handle it).
+ const hrefHasLocale = router.locales?.some(
+ (locale) => href.startsWith(`/${locale}/`) || href === `/${locale}`
+ );
+
return (