# General Translation Knowledge Base: How do I migrate from next-intl to gt-next?
URL: https://generaltranslation.com/en-GB/kb/migrate-from-next-intl.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Swap next-intl's plugin, middleware, and provider for gt-next's, match its locale prefix setting, and load messages/en.json as a gt-next dictionary so existing ICU messages and translations keep working. Map any localised pathnames before deleting next-intl's routing, then move copy inline into `<T>` one route at a time.
Category: Internationalisation
Last updated: 2026-11-11
Authors: Kevin Wu
Published: 2026-11-11

next-intl and gt-next both localise Next.js App Router apps with locale-prefixed routes and Server Components, so the app&#39;s structure barely changes. Your next-intl messages are already ICU MessageFormat, which is also what gt-next dictionaries use. That lets you keep `messages/en.json` and your translated catalogues while you switch libraries, then move copy inline into [`<T>`](/docs/react/reference/components/t) at your own pace.

If your catalogues work and only producing translations is slow, you may not need to migrate. If you keep next-intl, the `gt` CLI translates its catalogues in place. (See [next-intl vs. gt-next](/kb/next-intl-vs-gt-next).)

## How do next-intl APIs map to gt-next?

| next-intl                                                                          | gt-next                                                                                                                                                                                                                                                                     |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `defineRouting({ locales, defaultLocale })`                                        | `defaultLocale` and `locales` in `gt.config.json`                                                                                                                                                                                                                           |
| `createNextIntlPlugin()` in `next.config.ts`                                       | [`withGTConfig`](/docs/react/nextjs/config) from `gt-next/config`                                                                                                                                                                                                           |
| `createMiddleware(routing)` in `proxy.ts`                                          | [`createNextMiddleware`](/docs/react/nextjs/reference/functions/create-next-middleware) from `gt-next/middleware`                                                                                                                                                           |
| `localePrefix: 'as-needed'` / `'always'`                                           | `prefixDefaultLocale: false` (the default) / `true`                                                                                                                                                                                                                         |
| `getRequestConfig` in `i18n/request.ts`                                            | A root `getLocale.ts` reading `next/root-params`, plus the `dictionary` option                                                                                                                                                                                              |
| `NextIntlClientProvider`                                                           | [`GTProvider`](/docs/react/reference/components/gt-provider) in `app/[locale]/layout.tsx`                                                                                                                                                                                   |
| `useTranslations('ns')`                                                            | [`useTranslations`](/docs/react/reference/hooks/use-translations)`('ns')` from `gt-next`                                                                                                                                                                                    |
| `await getTranslations('ns')`                                                      | `await` [`getTranslations`](/docs/react/nextjs/reference/functions/get-translations)`('ns')` from `gt-next/server`                                                                                                                                                          |
| `t.rich` and `t.markup`                                                            | [`<T>`](/docs/react/reference/components/t) wrapping the real JSX                                                                                                                                                                                                           |
| `useFormatter` / `getFormatter`                                                    | [`<Num>`](/docs/react/reference/components/num), [`<Currency>`](/docs/react/reference/components/currency), [`<DateTime>`](/docs/react/reference/components/datetime), and [`<RelativeTime>`](/docs/react/reference/components/relative-time), or ICU arguments in messages |
| [`useLocale` / `getLocale`](https://next-intl.dev/docs/usage/configuration#locale) | [`useLocale`](/docs/react/reference/hooks/use-locale) / [`getLocale`](/docs/react/nextjs/reference/functions/get-locale)                                                                                                                                                    |
| `setRequestLocale(locale)`                                                         | Not needed for pages and layouts with `getLocale.ts`; [`registerLocale`](/docs/react/nextjs/reference/functions/register-locale) for route handlers and image routes                                                                                                        |
| `Link` from `createNavigation`                                                     | [`Link`](/docs/react/nextjs/link) from `gt-next/link`                                                                                                                                                                                                                       |
| `useRouter` / `getPathname` / `redirect`                                           | Next.js&#39;s own APIs with [`useLocalizeHref`](/docs/react/nextjs/reference/hooks/use-localize-href) or [`getLocalizeHref`](/docs/react/nextjs/reference/functions/get-localize-href)                                                                                      |
| `NEXT_LOCALE` cookie                                                               | `generaltranslation.locale`, renameable with `headersAndCookies.localeCookieName`                                                                                                                                                                                           |

## Step 1: Replace the plugin and keep your messages

Install `gt-next` and the `gt` CLI, then list your languages in `gt.config.json`. Point `withGTConfig` at your existing source catalogue, and match next-intl&#39;s URL shape:

```ts title="next.config.ts"
import type { NextConfig } from 'next';
import { withGTConfig } from 'gt-next/config';

const nextConfig: NextConfig = {};

export default withGTConfig(nextConfig, {
  dictionary: './messages/en.json',
  prefixDefaultLocale: true, // next-intl's default, localePrefix: 'always'
  headersAndCookies: { localeCookieName: 'NEXT_LOCALE' }, // keep returning visitors' language
});
```

Leave out `prefixDefaultLocale` if you used `localePrefix: 'as-needed'`. Set routing options on `withGTConfig` rather than only on the middleware, because gt-next&#39;s `<Link>` reads them from there.

This example covers standard locale prefixes, such as `/de/about`. If your `defineRouting` call customises routes, carry each customisation over before Step 2 deletes it:

* **Localised pathnames** (`pathnames`, such as `/de/ueber-uns`): map each one with gt-next&#39;s [`pathConfig`](/docs/react/nextjs/reference/functions/create-next-middleware#path-config) option, which takes the same shape of a shared path and a localised path per locale.
* **Custom prefixes, domain-based routing, or `localePrefix: 'never'`:** gt-next has no direct equivalent. Decide on the new URLs and add permanent redirects from the old ones before you remove next-intl&#39;s routing.

To keep serving your reviewed translations, add a `loadDictionary.ts` file at the project root. gt-next detects it automatically:

```ts title="loadDictionary.ts"
export default async function loadDictionary(locale: string) {
  try {
    return (await import(`./messages/${locale}.json`)).default;
  } catch {
    return {}; // a new locale without a catalogue uses General Translation's translations
  }
}
```

Entries in these files take precedence. General Translation&#39;s translation renders for any key a catalogue lacks. If `gt.config.json` already lists these catalogues under `files`, remove that entry: gt-next now loads them as dictionaries, and each string should have one translation owner.

## Step 2: Swap the middleware, layout, and request config

Replace next-intl&#39;s middleware in `proxy.ts` (`middleware.ts` on Next.js 15 and earlier):

```ts title="proxy.ts"
import { createNextMiddleware } from 'gt-next/middleware';

export default createNextMiddleware();

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next).*)'],
};
```

Once its routes are mapped, delete `i18n/request.ts` and `i18n/routing.ts`. In their place, add a root `getLocale.ts` so pages and layouts can render statically without `setRequestLocale`:

```ts title="getLocale.ts"
import { locale } from 'next/root-params';

export default async function getLocale() {
  return await locale();
}
```

`next/root-params` needs Next.js 15.5 or later; see the [middleware guide](/docs/react/nextjs/app-router-middleware#root-locale) for 15.2 to 15.4. Then replace the provider in your locale layout:

```tsx title="app/[locale]/layout.tsx"
import { GTProvider, getLocales } from 'gt-next';
import { getLocale } from 'gt-next/server';

export function generateStaticParams() {
  return getLocales().map((locale) => ({ locale }));
}

export default async function LocaleLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang={await getLocale()}>
      <body>
        <GTProvider>{children}</GTProvider>
      </body>
    </html>
  );
}
```

By default, gt-next falls back to `defaultLocale` for an unsupported locale segment. To return a 404 as next-intl&#39;s `hasLocale` check does, test the segment with [`isLocaleSupported`](/docs/react/nextjs/reference/functions/is-locale-supported) and call `notFound()`.

## Step 3: Change the imports

With the catalogue loaded as a dictionary, most components change only their import line:

```tsx
// Before
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { Link } from '@/i18n/navigation';

// After
import { useTranslations } from 'gt-next';
import { getTranslations } from 'gt-next/server';
import Link from 'gt-next/link';
```

`t('heading')`, namespaces, `{name}` placeholders, and ICU `plural` and `select` messages work unchanged, including in `generateMetadata`. Three patterns need edits:

* **`t.rich` and `t.markup`.** gt-next&#39;s [`t()`](/docs/react/reference/hooks/use-translations) returns a string. Move rich text inline into [`<T>`](/docs/react/reference/components/t) with real elements.
* **`t.raw` with arrays.** gt-next has no `t.raw`, and it reads a dictionary array only as a message with its options. Restructure lists as nested objects and read them with [`t.obj()`](/docs/react/guides/translating-with-dictionaries#objects) or one key at a time.
* **Missing keys.** next-intl logs an error and renders a fallback; gt-next throws, so search for a key before you delete it from the catalogue.

Then run [`npx gt validate`](/docs/cli/reference/commands/validate) to check for entries that aren&#39;t valid ICU, and [`npx gt translate --dictionary ./messages/en.json`](/docs/cli/reference/commands/translate) to generate translations for keys your catalogues lack and for any new locales. The CLI finds a dictionary on its own only when it&#39;s named `dictionary.json`, `.ts`, or `.js` in the project root or `src/`, so pass `--dictionary ./messages/en.json` to both commands, locally and in CI, for as long as you keep the catalogue:

```bash
npx gt validate --dictionary ./messages/en.json
npx gt translate --dictionary ./messages/en.json
```

## Step 4: Move copy inline, one route at a time

Keys keep working, so you can stop here. To keep copy beside the JSX it renders, move it into [`<T>`](/docs/react/reference/components/t) route by route, starting with `t.rich` messages:

```tsx
// Before: "terms": "By continuing you accept the <link>terms</link>."
t.rich('terms', { link: (chunks) => <a href="/terms">{chunks}</a> });

// After
<T>
  By continuing you accept the <a href="/terms">terms</a>.
</T>
```

In async Server Components, use [`getGT`](/docs/react/nextjs/reference/functions/get-gt) for plain strings. The same [`npx gt translate`](/docs/cli/reference/commands/translate) command generates new translations for copy you move inline, using the surrounding JSX as context. Reviewers can correct them in the [Dashboard](/docs/platform/dashboard/guides/reviewing-translations), and a [Context Group](/docs/platform/dashboard/guides/defining-context-for-translations) Glossary keeps your terminology. Delete each key from your catalogues once nothing reads it.

## Step 5: Remove next-intl

When a search for `next-intl` returns nothing, remove the dependency and `createNextIntlPlugin`, and test a production build in every locale. Then add [`npx gt translate`](/docs/cli/reference/commands/translate) to your build or [CI](/kb/translations-in-ci) so new copy ships translated, with `--dictionary ./messages/en.json` if you kept the catalogue.

## Common questions

### Will my URLs change?

Not for standard locale prefixes, if you match next-intl&#39;s setting: `localePrefix: 'as-needed'` corresponds to gt-next&#39;s default, and `'always'` to `prefixDefaultLocale: true`. gt-next also uses an `app/[locale]` segment. Localised pathnames need an explicit `pathConfig` mapping, and custom prefixes and domain-based routing have no direct gt-next equivalent, so redirect those URLs and test every route before removing next-intl&#39;s routing.

### Do I lose my existing translations?

No. Load your next-intl catalogues with [`loadDictionary`](/docs/react/reference/functions/load-dictionary), and they render as before. General Translation translates only the keys they&#39;re missing, and any copy you later move inline into [`<T>`](/docs/react/reference/components/t).

### Does gt-next support static rendering?

Yes. Add a root `getLocale.ts` and `generateStaticParams`. (See [static site generation](/docs/react/nextjs/app-router-static-site-generation).)

### Is gt-next free?

gt-next is MIT-licensed and renders bundled translations without an account. Generating translations with General Translation is [usage-based](https://generaltranslation.com/pricing).

## Key takeaways

* Replace next-intl&#39;s plugin, middleware, request config, and provider with `withGTConfig`, [`createNextMiddleware`](/docs/react/nextjs/reference/functions/create-next-middleware), `getLocale.ts`, and [`GTProvider`](/docs/react/reference/components/gt-provider).
* Keep `messages/en.json` as a gt-next dictionary and your translated catalogues through [`loadDictionary`](/docs/react/reference/functions/load-dictionary); ICU messages work unchanged.
* Match `localePrefix` with `prefixDefaultLocale` and keep the `NEXT_LOCALE` cookie so standard locale URLs and saved preferences carry over; map localised pathnames with `pathConfig` before deleting next-intl&#39;s routing.
* Rewrite `t.rich` as [`<T>`](/docs/react/reference/components/t), then move other copy inline one route at a time.

## Related questions

- [next-intl alternatives: gt-next vs. next-intl](https://generaltranslation.com/en-GB/kb/next-intl-vs-gt-next.mdx): next-intl renders translations you supply in a message catalogue for each language. gt-next renders translations too, and General Translation also generates them from copy written inline in your JSX. Both are MIT-licensed and support the App Router and Server Components. If your next-intl catalogues already work, you can keep them and use General Translation to translate them without migrating.
- [How do I add i18n to a Next.js App Router app?](https://generaltranslation.com/en-GB/kb/add-i18n-to-nextjs.mdx): Start with gt init to configure General Translation in a supported App Router project, then mark source content, generate translations and verify language switching. gt-next keeps copy in JSX with translation context; the CLI, Dashboard editor and Locadex handle generation, review and ongoing updates. This guide also shows the explicit local-file set-up.
- [How do I set up SEO for a multilingual Next.js app?](https://generaltranslation.com/en-GB/kb/multilingual-nextjs-seo.mdx): Give each language its own prefixed URL, such as /en/about and /es/about, then publish html lang, a self-referencing canonical, hreflang alternates with x-default, a translated title and description, and a sitemap entry for each published language. Mark unpublished versions noindex. General Translation's gt-next routes each language to its own URL and translates titles with getGT.
- [Language redirects and SEO: URL, cookie, or Accept-Language?](https://generaltranslation.com/en-GB/kb/locale-routing-seo.mdx): Let the URL decide the language of every page you want indexed, because Googlebot sends no Accept-Language header and crawls mostly from US addresses. Use a saved cookie choice, then Accept-Language, only to pick where a neutral root such as / redirects, with a temporary 302 or 307.
- [How do I keep translations up to date on every deploy?](https://generaltranslation.com/en-GB/kb/translations-in-ci.mdx): Run translation as a pipeline step before each production build, so new copy ships translated with the deploy that adds it. General Translation's gt CLI does this with npx gt translate, using a project API key and project ID stored as CI secrets.

## Previous and next questions

- Previous question: [Which translation tools charge for usage instead of seats?](https://generaltranslation.com/en-GB/kb/translation-pricing-without-seat-fees.mdx)
- Next question: [How do I migrate from react-i18next to gt-react?](https://generaltranslation.com/en-GB/kb/migrate-from-react-i18next.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/en-GB/kb/sitemap.md) for every article.
