-
-
Notifications
You must be signed in to change notification settings - Fork 246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integration with multi-tenancy #1107
Comments
Our use case is usually having the opposite '/[locale]/[market]'. Which works-ish so far. This is of course a bit more complicated as you write 😅 |
Do you have some details about the parts that don't work? Which That being said, do you support an infinite number of markets? Otherwise, with the newly added capability for customization of prefixes, maybe you could define them statically. E.g.: import {LocalePrefix} from 'next-intl/routing';
export const locales = ['en-US', 'en-UK', /* ... */] as const;
export const localePrefix = {
mode: 'always',
prefixes: {
'en-US': '/en/us',
'en-UK': '/en/uk'
// ...
}
} satisfies LocalePrefix<typeof locales>; See also: Can I read the matched prefix in my app? |
I really love the multi-tenancy support in mind for this library, but I am wondering if the tenant could change the default locale, and the supported locales also managed by the tenant how it would be configured? |
@yaman3bd Let's continue the conversation from #532 (reply in thread) here since it's more related to multi-tenancy than switching locales. What you've shared in the other thread: App structure:
Notes:
My question would be: Can you provide some examples how the URLs look like in the browser address bar for the user? Do you wish to implement any rewrites that would hide the |
the urls: but right now my production app is still on Page Router and I am not implementing any rewrites or redirects for the URLs I just get the host header in |
Thanks, that helps! What is your motivation for the Here's an example that should work if you only have a top-level // middleware.tsx
import createIntlMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';
export default async function middleware(request: NextRequest) {
const tenant = await getTenant(request);
const handleI18nRouting = createIntlMiddleware({
locales: tenant.locales,
localePrefix: 'as-necessary',
defaultLocale: tenant.defaultLocale
});
const response = handleI18nRouting(request);
return response;
}
// ...
That being said, const rewrite = response.headers.get('x-middleware-rewrite');
if (rewrite) {
response.headers.set('x-middleware-rewrite', /* adapted URL with [domain] segment */)
} Let me know if that helps! |
this is really cool!, I did not know I could fetch data in the middleware! import { headers } from "next/headers";
import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
export default getRequestConfig(async ({ locale }) => {
const host = headers.get("host");
const tenant = await getTenant(host);
const locales = tenant.supported_locales;
if (!locales.includes(locale as any)) notFound();
const messages = await getMessages(host, locale);
return {
messages: messages
};
}); but I am wondering if the user navigates to another page is
I think it helps for better isolation for the tenant so the data does not get mixup, and when I want to do a client-side fetch or mutation I need to send the tenant domain with the request headers |
A middleware can return a 404 status code, but unfortunately not a rendered 404 page. So if you need that, you'd have to call
Yep!
I see! Depending on your setup that could still happen, so the better choice is to not assign anything to global singleton instances. If you use So if static rendering is not a concern, you might be able to achieve a slighter easier setup by avoiding the Hope this helps! |
Our use-case has multiple TLDs (different regions for a storefront) with different kinds of locales. Currently we read the host and based on that determine the tenant/region, but this makes almost all pages dynamic. We could of course create duplicate instances with an ENV for the region, however we already have 8 storefronts and in the future even more. Having a [domain] part that is rewritten/hidden in this library to also enable static rendering would be really great! |
@pepijn-vanvlaanderen Have you by chance tried adapting |
Sorry for the late reply. E.g Now I am making some poor extension in the middleware to automatically redirect to the correct market, as well as the routing is a bit cumbersome as we have to construct the url manually. Again, everything is possible to solve in user-land, but it will most likely be worse than what this library offers for locales. Our market parameter contains information on both the country, but also the customer segment (B2B/B2C). Might not be the best approach but that is what we have now. My approach to multi-tenancy might be a bit different to the use-cases of others as well 😅 |
@amannn I was indeed able to make this work, thanks! Also for anyone trying the same solution, I also disabled |
@amannn it worked as a charm, thanks! I have completed the setup and it is working fine, but I had an issue with import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
import { fetchTenant } from "@/app/fetch/services/tenant-service";
import { fetchTranslations } from "@/app/fetch/services/translations-service";
export default getRequestConfig(async ({ locale }) => {
const tenant = await fetchTenant();
if (!tenant) notFound();
const locales = tenant.supported_locales.map((locale) => locale.code);
const defaultLocale = tenant.locale;
if (!locales.includes(locale)) notFound();
const translations = (await fetchTranslations());
return {
messages: translations[locale] ?? translations[defaultLocale]
};
}); I could not find a way to export the locales from |
Awesome! 🙌
For this very use case, you can not pass |
Thank you for your response! |
@amannn what about this use case:
in this case, both so the pattern would be |
@sirajtahra I think custom prefixes could work for this: import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en-US-x-saudi', 'en-US-x-uae', 'ar-SA-x-saudi', 'ar-SA-x-uae'],
defaultLocale: 'en-US-x-saudi',
localePrefix: {
mode: 'always',
prefixes: {
'en-US-x-saudi': '/saudi-ar',
'en-US-x-uae': '/uae-ar',
'ar-SA-x-saudi': '/saudi-ar',
'ar-SA-x-uae': '/uae-ar'
}
}
}); The one restriction is that prefixes and locales need a 1:1 mapping, therefore I've added a private tag. |
Thank you @amannn for the suggestion 🙏 This should cover my use case |
@amannn I've tried the new custom prefixes feature, but was unable to set it up correctly. I'd love to get some help understanding it. I applaud the change, but I have to admit that the configuration feels a bit scattered - one needs to read a whole lot of documentation to set everything up, as opposed to it magically working :D I've been struggling with the following:
In this case the domains configuration would contain an entry much like this one: {
"domain": "http://domain.com",
"defaultLocale": "da-DK",
"locales": ["da-DK", "en-DK"],
},
{
"domain": "http://domain.co.uk",
"defaultLocale": "en-GB",
"locales": ["en-GB"],
},
// ...
"localePrefix": "as-needed", At first I thought it had worked fine, but In reality it seems to display danish pages on the .co.uk website and does weird things with the pathnames (fx This used to work perfectly fine before the change (because I made my own middleware back in the day to do custom prefixes, but now I'm struggling to figure out how the domains configuration should work. I've also seen documented usage of |
Hey @markomitranic, hmm that doesn't sound right, yes. Are you also using custom prefixes since you mentioned that in your comment? But yes, I'll spin up some tests to look a bit further into this … As a side note, I really found the combination of |
@markomitranic I've added #1594 to try to set up a failing test. You think you could help me out there? |
Is your feature request related to a problem? Please describe.
next-intl
currently requires a top-level[locale]
segment. While we support custom prefixes like/uk
→/en-gb
with #1086, users have expressed the desire to add additional segments likeapp/[tenant]/[locale]
.Related: #1055
(this issue was extracted from #653)
Describe the solution you'd like
The solution here really depends on if:
[tenant]
segment comes before the[locale]
segment[tenant]
segment is visible to the user in the public pathnameAd 1) If the
[tenant]
segment comes after the[locale]
segment, this should already work. The only assumption fromnext-intl
is that the[locale]
segment comes right at the beginning.Ad 2) In case you support a finite number of tenants that are known at build time, you could use custom prefixes to define them as necessary (e.g.
'en-US-x-tenant1': '/tenant1/en-US'
). Alternatively, you could also deploy your app separately for each tenant and set abasePath
for each tenant.Ad 3) If the
[tenant]
segment is not visible to the user, you should already be able to implement this via composing the middleware or if you anyway have a requirement for static rendering, you could consider usingunstable_setRequestLocale
to pass alocale
that depends on the tenant.Ad 4) This use case is already supported pretty well via the
domains
config option.Therefore many use cases should already be possible. The one that remains is where the routing looks like
[tenant]/[locale]
, you support a dynamic number of tenants and the tenant is visible to the user in the pathname (e.g./tenant1/en
).Continuing on the work from #1086, maybe we could support a configurable pattern that is incorporated by the middleware and the navigation APIs.
Example API:
Since we have two or more dynamic segments following each other,
next-intl
requires knowledge about which segment to retrieve the locale from.It would be great to research how common this pattern is, therefore please leave a thumbs up here, leave a comment with your use case and possibly subscribe to updates if this is relevant for you!
Related discussions
Describe alternatives you've considered
If you're out of other options, you could also consider providing your own implementation for the middleware and navigation APIs (see e.g. #609). Note that you can still use component APIs like
useTranslations
in this case.The text was updated successfully, but these errors were encountered: