
Security News
NVD Concedes Inability to Keep Pace with Surging CVE Disclosures in 2025
Security experts warn that recent classification changes obscure the true scope of the NVD backlog as CVE volume hits all-time highs.
@luudjanssen/next-locale-router
Advanced tools
Next.js v10 introduced [internationalized routing](https://nextjs.org/docs/advanced-features/i18n-routing) support. However, it's limited:
Next.js v10 introduced internationalized routing support. However, it's limited:
defaultLocale
for subpaths, meaning always one locale will be available on /
instead of /${locale}
. next.js#18419nl-BE
locale will be available under /nl-BE
, it can't be changed to /nl
for example. next.js#17078 (comment)It also contains a couple of bugs where the locale of domain X (/nl-BE
) is also available under domain Y. So accessing domain-y.com/nl-BE works, even though nl-BE
is not part of domain Y's configuration.
This package solves these problems by exposing Express middleware to route these use cases in the desired way.
Note: This package uses hacks that change Next.js internals to make this work. It's pretty well tested, but it's important to note that we only support Next.js version 10.2.0. If you use any other version, we can't guarentee routing will work.
This package requires you create a custom server.js
file for your Next.js project and create a custom i18n.config.js
file in the root of your project.
The configuration for the router is a superset of the Next.js i18n config. Create the i18n.config.js
file in the root of your project and follow this syntax:
const dutchDomain = {
hostname: "nextjs.dutch",
defaultLocale: "nl",
subpaths: [
{
locale: "nl",
path: "/",
},
],
}
const belgianDomain = {
hostname: "nextjs.belgian",
defaultLocale: "nl-BE",
subpaths: [
{
locale: "nl-BE",
path: "/nl/",
},
{
locale: "fr",
path: "/fr/",
},
],
}
const domains = [dutchDomain, belgianDomain]
const defaultLocale = "nl"
module.exports = {
domains,
defaultLocale,
}
Since this configuration file is a superset of the Next.js i18n config we can create the Next.js config for you. This will result in the following next.config.js
file:
const { withLocaleRouter } = require("@incentro/next-locale-router")
module.exports = withLocaleRouter()({
// Next.js config
})
Or, if you're using next-compose-plugins:
const withPlugins = require("next-compose-plugins")
const { withLocaleRouter } = require("@incentro/next-locale-router")
module.exports = withPlugins([withLocaleRouter()], {
// Next.js config
})
Next to the config.domains
and config.defaultLocale
options (which are required), you have the following options:
config.ignore
A function that allows you to force certain routes to be ignored by the locale router. It receives a parsed url as its parameter and should return a boolean:
const ignore = (url) => {
return url.pathname === "favicon.ico"
}
module.exports = {
domains,
defaultLocale,
ignore: (url) => ignore,
}
A common use case for this ignore parameter is to ignore routes in the public
. Currently, next-locale-router
doesn't ignore items in the public
directory, because we can't easily determine what is a pages
route and what is a public
route and we don't want to rewrite Next.js's internals. For most public routes, this isn't a problem, because the routes are still accessible.
Take for example the favicon (favicon.ico
). When a user is accessing the /favicon.ico
route, but should be on the Dutch locale at /nl/
, the request will be redirected to /nl/favico.ico
and the image will still successfully be resolved.
The problem here is that our server will be handing out a lot of redirects and the client needs to do unnecessary redirects. You can add known public routes to the config.ignore
option to prevent these redirects.
Create a server.js
file in the root of the project, as per the Next.js standard. This example is an adapation of the custom-server-express Next.js template:
const next = require("next")
const express = require("express")
const { config, createLocaleMiddleware } = require("@incentro/next-locale-router")
const dev = process.env.NODE_ENV !== "production"
const app = next({ dev })
const handle = app.getRequestHandler()
const localeMiddleware = createLocaleMiddleware(config, app)
app.prepare().then(() => {
const server = express()
// Trust proxy is required if you're running behind a reverse proxy
// This is because we need to know the original hostname before the request was proxied.
server.set("trust proxy", true)
server.use(localeMiddleware)
server.all("*", (request, response) => handle(request, response))
server.listen(3000, (err) => {
if (err) throw err
console.log("> Ready on http://localhost:3000")
})
})
next-i18next
The scopes of next-locale-router
and next-i18next
don't really overlap. next-locale-router
handles routing and next-i18next
receives the locale from Next.js and simply gets the translations. The only part where they overlap is in the configuration. next-i18next
and next-locale-router
share most of their config. Therefore we created a plugin to generate part of the next-i18next
configuration for you:
next-i18next.config.js
const { configToNextI18NextConfig } = require("@luudjanssen/next-locale-router/next-i18next")
const config = require("./i18n.config")\
module.exports = {
i18n: configToNextI18NextConfig(config),
// ... Other next-i18next specific configuration
}
next-translate
The scopes of next-locale-router
and next-translate
don't really overlap. next-locale-router
handles routing and next-translate
receives the locale from Next.js and simply gets the translations. The only part where they overlap is in the configuration. next-translate
and next-locale-router
share most of their config. Therefore we created a plugin to generate part of the next-translate
configuration for you:
i18n.js
const { configToNextTranslateConfig } = require("@luudjanssen/next-locale-router/next-translate")
const config = require("./i18n.config")
const path = require("path")
module.exports = {
...configToNextTranslateConfig(config),
// ... Other next-translate specific configuration
}
<Link>
componentOn the client side we also need to rewrite the URL's. We do this by exposing a custom <Link>
component, just like Next.js's <Link>
component. It supports the exact same props as next/link, so you can just update your imports:
- import Link from "next/link"
+ import Link from "@incentro/next-locale-router/link"
Just like the custom <Link>
component, we also need to update our URL's when using the client side router directly. We do this by wrapping next/router. The only thing you need to do is to update your next/router
imports:
- import SingletonRouter from "next/router"
+ import SingletonRouter from "@incentro/next-locale-router/router"
useRouter
hook- import { useRouter } from "next/router"
+ import { useRouter } from "@incentro/next-locale-router/router"
getServerSideProps
and getStaticProps
redirectsWhen redirecting with getServerSideProps
or getStaticProps
it's important to redirect to the right destination for the locale to prevent double redirects or even worse, a redirect loop. When creating redirects in getServerSideProps
or getStaticProps
you can use the addLocaleToRedirect
method exported by this package.
import { addLocaleToRedirect } from "@incentro/next-locale-router/props"
export const getServerSideProps: GetServerSideProps = ({ locale }) => {
// This method rewrites the /about route to the right route for the given locale, e.g. `/nl/about`
const redirect = addLocaleToRedirect(
{
destination: "/about",
permanent: true,
},
locale,
)
return { redirect }
}
This project also exposes a utility method for stripping the locale from a path. This can be helpful to test for certain routes in your code. For example when checking the url of the getServerSideProps
or getStaticProps
context you'll see it includes the locale:
import { GetServerSideProps } from "next"
const getServerSideProps: GetServerSideProps = ({ resolvedUrl }) => {
console.log(resolvedUrl) // This would log "/nl/about" for the "pages/about.tsx" page
}
We can use the removeLocaleFromPath
method from @incentro/next-locale-router/props
to remove the /nl
prefix:
import { GetServerSideProps } from "next"
import { removeLocaleFromPath } from "@incentro/next-locale-router/props"
const getServerSideProps: GetServerSideProps = ({ resolvedUrl }) => {
const urlWithoutLocale = removeLocaleFromPath(resolvedUrl)
console.log(urlWithoutLocale) // This would log "/about" for the "pages/about.tsx" page
}
If you want some additional debugging output in your console, set the NEXT_PUBLIC_LOCALE_ROUTER_DEBUG=true
environment variable before starting the node process.
The middleware method receives the request and has to decide on one of the following strategies:
server.js
example, it should be handled by Next.js, not by the middleware.This project consists of a strategy investigator and a strategy handler. The investigator determines what strategy is necessary and the handler actually executes the strategy. You can find them under src/strategy/strategy-investigator.class.ts
and src/strategy/strategy-handler.class.ts
respectively.
The strategy investigator follows the following steps to determine the strategy:
_next
for example)?
config.ignore
option?
/nl-BE/
to /nl/
.
Accept-Language
header, as well checking for the NEXT_LOCALE
cookie set by Next.js. Otherwise it falls back to the defaultLanguage
for the given domain.<Link>
componentBesides having the server redirect URL's we also need to control client side routing. We do this by wrapping Next.js's <Link>
component.
src/client/link/link.tsx
→ We use Next.js own <Link>
component, but provide our own <LinkLocaleRewriter>
as its child and we make sure we pass the href
prop.
Next.js's <Link>
component sets a couple of properties on its child, like the href
prop and an onClick
handler.
We wrap the onClick
handler to make sure we can execute some code whenever a user click a <Link>
.
src/client/link/util/wrap-click-handler-with-rewrite.ts
→ Whenever the user clicks on a <Link>
we subscribe to Next.js router's beforeHistoryChange
event which Next.js will fire.
When this event fires we trigger our own window.history.pushState
instead of the one that Next.js would execute. This simply sets the browser's URL to the value we want.
How do we prevent Next.js from changing the URL? Well, here is where things get hacky. Check src/client/link/util/disable-history-push-state-for-one-tick.ts
. This method overwrites the browser's window.history.pushState
for one Javascript "tick". Because the beforeHistoryChange
method is executed just before Next.js does its own history pushing, we point Next.js to an empty method. We use setTimeout
to ensure we reinstate window.history.pushState
in the next Javascript "tick".
I know, it's pretty hacky, but it's the only way I could update te client URL's without rewriting Next.js's <Link>
component or showing a URL change to the user.
The wrapper of next/router
works about the same as the <Link>
component. We temporarily disable window.history.pushState
for Next.js's own router and execute the state update ourselves. The only difference is that in this case we wrap the router.push
and router.replace
methods using a proxy. You can find the code for this at src/client/router/util/wrap-router-with-rewrites.ts
.
<Link>
component that supports the configuration.next/router
getServerSideProps()
and getStaticProps()
public
directory?FAQs
Next.js v10 introduced [internationalized routing](https://nextjs.org/docs/advanced-features/i18n-routing) support. However, it's limited:
We found that @luudjanssen/next-locale-router demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Security experts warn that recent classification changes obscure the true scope of the NVD backlog as CVE volume hits all-time highs.
Security Fundamentals
Attackers use obfuscation to hide malware in open source packages. Learn how to spot these techniques across npm, PyPI, Maven, and more.
Security News
Join Socket for exclusive networking events, rooftop gatherings, and one-on-one meetings during BSidesSF and RSA 2025 in San Francisco.