Skip to content

Commit 2a18b32

Browse files
feat: add redirectStatusCode option (#3685)
Co-authored-by: Bobbie Goede <bobbiegoede@gmail.com>
1 parent bcaf220 commit 2a18b32

8 files changed

Lines changed: 57 additions & 3 deletions

File tree

docs/content/docs/04.api/00.options.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,13 @@ Set to a path to which you want to redirect users accessing the root URL (`'/'`{
234234
}
235235
```
236236

237+
## redirectStatusCode
238+
239+
- type: `number`{lang="ts-type"}
240+
- default: `302`{lang="ts"}
241+
242+
Specifies the HTTP status code to use when redirecting to a localized route from any URL except the root URL ('/').
243+
237244
## langDir
238245

239246
- type: `string`{lang="ts-type"}
@@ -614,4 +621,4 @@ Used to configure the directory used to resolve i18n files.
614621

615622
::callout{icon="i-heroicons-exclamation-triangle" color="warning"}
616623
This feature relies on [Nuxt's Auto-imports](https://nuxt.com/docs/guide/concepts/auto-imports) and will not work if this has been disabled.
617-
::
624+
::
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { test, expect, describe } from 'vitest'
2+
import { fileURLToPath } from 'node:url'
3+
import { setup, fetch } from '../utils'
4+
5+
await setup({
6+
rootDir: fileURLToPath(new URL(`../fixtures/basic`, import.meta.url)),
7+
browser: true,
8+
nuxtConfig: {
9+
i18n: {
10+
strategy: 'prefix',
11+
defaultLocale: 'en',
12+
detectBrowserLanguage: {
13+
redirectOn: 'no prefix'
14+
},
15+
rootRedirect: { statusCode: 418, path: 'test-route' },
16+
redirectStatusCode: 307
17+
}
18+
}
19+
})
20+
21+
describe('redirectStatusCode', () => {
22+
test('uses custom status code', async () => {
23+
const res = await fetch('/about', { redirect: 'manual' })
24+
expect(res.status).toBe(307)
25+
expect(res.headers.get('location')).toEqual('/en/about')
26+
})
27+
28+
test('does not affect root redirect option', async () => {
29+
const rootRes = await fetch('/', { redirect: 'manual' })
30+
expect(rootRes.status).toEqual(418)
31+
expect(rootRes.headers.get('location')).toEqual('/en/test-route')
32+
})
33+
})

src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const DEFAULT_OPTIONS = {
6262
strategy: STRATEGY_PREFIX_EXCEPT_DEFAULT,
6363
langDir: 'locales',
6464
rootRedirect: undefined,
65-
65+
redirectStatusCode: 302,
6666
detectBrowserLanguage: {
6767
alwaysRedirect: false,
6868
cookieCrossOrigin: false,

src/prepare/runtime-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function prepareRuntimeConfig(ctx: I18nNuxtContext, nuxt: Nuxt) {
1111
baseUrl: ctx.options.baseUrl,
1212
defaultLocale: ctx.options.defaultLocale,
1313
rootRedirect: ctx.options.rootRedirect,
14+
redirectStatusCode: ctx.options.redirectStatusCode,
1415
skipSettingLocaleOnNavigate: ctx.options.skipSettingLocaleOnNavigate,
1516
locales: ctx.options.locales,
1617
detectBrowserLanguage: ctx.options.detectBrowserLanguage ?? DEFAULT_OPTIONS.detectBrowserLanguage,

src/runtime/context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface NuxtI18nContext {
3737
/** SSG with dynamic locale resources */
3838
dynamicResourcesSSG: boolean
3939
rootRedirect: { path: string; code: number } | undefined
40+
redirectStatusCode: number
4041
/** Get default locale */
4142
getDefaultLocale: () => string
4243
/** Get current locale */
@@ -109,6 +110,7 @@ export function createNuxtI18nContext(nuxt: NuxtApp, vueI18n: I18n, defaultLocal
109110
preloaded: false,
110111
config: runtimeI18n,
111112
rootRedirect: resolveRootRedirect(runtimeI18n.rootRedirect),
113+
redirectStatusCode: runtimeI18n.redirectStatusCode ?? 302,
112114
dynamicResourcesSSG: !__IS_SSR__ || (!__I18N_FULL_STATIC__ && (import.meta.prerender || __IS_SSG__)),
113115
getDefaultLocale: () => defaultLocale,
114116
getLocale: () => unref(i18n.locale),

src/runtime/server/plugin.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ function* detect(
3838
if (__I18N_ROUTING__) {
3939
yield { locale: detectors.route(path), source: 'route' }
4040
}
41+
42+
yield { locale: detection.fallbackLocale, source: 'fallback' }
4143
}
4244

4345
export default defineNitroPlugin(async nitro => {
@@ -96,6 +98,7 @@ export default defineNitroPlugin(async nitro => {
9698
break
9799
}
98100
}
101+
locale ||= defaultLocale
99102

100103
function getLocalizedMatch(locale: string) {
101104
const res = matchLocalized(path || '/', locale, defaultLocale)
@@ -112,6 +115,8 @@ export default defineNitroPlugin(async nitro => {
112115
(isSupportedLocale(detector.route(rootRedirect.path)) && rootRedirect.path) ||
113116
matchLocalized(rootRedirect.path, locale, defaultLocale)
114117
redirectCode = rootRedirect.code
118+
} else if (runtimeI18n.redirectStatusCode) {
119+
redirectCode = runtimeI18n.redirectStatusCode
115120
}
116121

117122
switch (detection.redirectOn) {

src/runtime/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ export function navigate(nuxtApp: NuxtApp, to: CompatRoute, locale: string) {
283283
return
284284
}
285285

286-
return navigateTo(destination)
286+
return navigateTo(destination, { redirectCode: ctx.redirectStatusCode })
287287
}
288288

289289
export function prefixable(currentLocale: string, defaultLocale: string): boolean {

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ export type NuxtI18nOptions<
177177
*/
178178
overrides?: Omit<NuxtI18nOptions<Context>, 'overrides'>
179179
rootRedirect?: string | RootRedirectOptions
180+
/**
181+
* Status code used for localized redirects
182+
* @default 302
183+
*/
184+
redirectStatusCode?: number
180185
skipSettingLocaleOnNavigate?: boolean
181186
/**
182187
* @deprecated This option is deprecated, only `'composition'` types will be supported in the future.
@@ -362,6 +367,7 @@ export interface I18nHeadMetaInfo {
362367
export interface I18nPublicRuntimeConfig {
363368
baseUrl: NuxtI18nOptions['baseUrl']
364369
rootRedirect: NuxtI18nOptions['rootRedirect']
370+
redirectStatusCode?: NuxtI18nOptions['redirectStatusCode']
365371
domainLocales: { [key: Locale]: { domain: string | undefined } }
366372
/** @internal Overwritten at build time, used to pass generated options to runtime */
367373
locales: NonNullable<Required<NuxtI18nOptions<unknown>>['locales']>

0 commit comments

Comments
 (0)