From b2f1f04b5200e3181ad8b4e11258ed60753a4df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ch=C5=82odnicki?= Date: Wed, 3 Jun 2020 13:53:09 +0200 Subject: [PATCH] fix: initial redirect breaks reactivity in static mode When we've detected browser language and need to redirect we need to do it from the middleware for the page to transition correctly. The problem is Nuxt doesn't trigger middleware for initial navigation in static mode (the idea is that it already ran when generating page on the server), so we can't do it properly. Fixed by hooking into the VueRouter "beforeEach" navigation hook so that we can handle it from the right place. Still, as Nuxt doesn't know that we are redirecting, it would throw some errors trying to load previous page. So use "location.assign()" to "hard" redirect. Resolves #737 --- src/templates/middleware.js | 6 +++++- src/templates/plugin.main.js | 31 +++++++++++++++++++++--------- test/browser.test.js | 29 +++++++++++++++++++++++++++- test/fixture/basic/pages/index.vue | 5 +++++ test/fixture/basic/pages/posts.vue | 5 +++++ 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/templates/middleware.js b/src/templates/middleware.js index 9770cac2e..f5b3464fa 100644 --- a/src/templates/middleware.js +++ b/src/templates/middleware.js @@ -1,5 +1,6 @@ import middleware from '../middleware' +/** @type {import('@nuxt/types').Middleware} */ middleware.nuxti18n = async (context) => { const { app, isHMR } = context @@ -7,5 +8,8 @@ middleware.nuxti18n = async (context) => { return } - await app.i18n.__onNavigate() + const [status, redirectPath] = await app.i18n.__onNavigate(context.route) + if (status && redirectPath) { + context.redirect(status, redirectPath) + } } diff --git a/src/templates/plugin.main.js b/src/templates/plugin.main.js index 54a7c3f84..92b33a837 100644 --- a/src/templates/plugin.main.js +++ b/src/templates/plugin.main.js @@ -160,9 +160,7 @@ export default async (context) => { } // Called by middleware on navigation (also on the initial one). - const onNavigate = async () => { - const { route } = context - + const onNavigate = async route => { // Handle root path redirect if (route.path === '/' && rootRedirect) { let statusCode = 302 @@ -173,15 +171,13 @@ export default async (context) => { path = rootRedirect.path } - redirect(statusCode, `/${path}`, route.query) - return + return [statusCode, `/${path}`] } const storedRedirect = app.i18n.__redirect if (storedRedirect) { app.i18n.__redirect = null - redirect(storedRedirect) - return + return [302, storedRedirect] } app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context) @@ -191,6 +187,8 @@ export default async (context) => { getLocaleFromRoute(route) || app.i18n.locale || app.i18n.defaultLocale || '' await app.i18n.setLocale(finalLocale) + + return [null, null] } const extendVueI18nInstance = i18n => { @@ -248,8 +246,23 @@ export default async (context) => { finalLocale = detectedBrowserLocale || finalLocale await loadAndSetLocale(finalLocale, { initialSetup: true }) - // Trigger onNavigate manually for Nuxt generate in universal mode as Nuxt doesn't do that on load. if (process.client && process.static && IS_UNIVERSAL_MODE) { - await onNavigate() + // Trigger onNavigate manually for Nuxt generate in universal mode as Nuxt doesn't + // do that on initial load. + const unregisterHook = app.router.beforeEach(async (to, from, next) => { + setTimeout(unregisterHook, 0) + + if (!from.name) { + const redirectTo = (await onNavigate(to))[1] + if (redirectTo) { + // Not using "next()" to redirect since that would leave Nuxt in a weird state, trying + // to load the old page and triggering errors. + location.assign(redirectTo) + return + } + } + + next() + }) } } diff --git a/test/browser.test.js b/test/browser.test.js index 72f5456a1..836618020 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -163,6 +163,33 @@ describe(`${browserString} (generate)`, () => { await navigate(page, '/') expect(await (await page.$('body')).textContent()).toContain('locale: en') }) + + // Issue https://github.com/nuxt-community/nuxt-i18n/issues/737 + test('reactivity works after redirecting to detected browser locale (root path)', async () => { + page = await browser.newPage({ locale: 'fr' }) + await page.goto(server.getUrl('/')) + expect(page.url()).toBe(server.getUrl('/fr/')) + // Need to delay a bit due to vue-meta batching with 10ms timeout. + await page.waitForTimeout(20) + expect(await page.title()).toBe('Accueil') + + await navigate(page, '/') + await page.waitForTimeout(20) + expect(await page.title()).toBe('Homepage') + }) + + test('reactivity works after redirecting to detected browser locale (sub-path)', async () => { + page = await browser.newPage({ locale: 'fr' }) + await page.goto(server.getUrl('/posts/')) + expect(page.url()).toBe(server.getUrl('/fr/articles/')) + // Need to delay a bit due to vue-meta batching with 10ms timeout. + await page.waitForTimeout(20) + expect(await page.title()).toBe('Articles') + + await navigate(page, '/posts/') + await page.waitForTimeout(20) + expect(await page.title()).toBe('Posts') + }) }) describe(`${browserString} (generate, prefix strategy)`, () => { @@ -252,7 +279,7 @@ describe(`${browserString} (generate with detectBrowserLanguage.fallbackLocale)` test('redirects to browser locale', async () => { page = await browser.newPage({ locale: 'fr' }) await page.goto(server.getUrl('/')) - expect(page.url()).toBe(server.getUrl('/fr')) + expect(page.url()).toBe(server.getUrl('/fr/')) expect(await (await page.$('body')).textContent()).toContain('locale: fr') }) }) diff --git a/test/fixture/basic/pages/index.vue b/test/fixture/basic/pages/index.vue index 9119d601b..52272c4d6 100644 --- a/test/fixture/basic/pages/index.vue +++ b/test/fixture/basic/pages/index.vue @@ -13,6 +13,11 @@ import LangSwitcher from '../components/LangSwitcher' export default { components: { LangSwitcher + }, + head () { + return { + title: this.$t('home') + } } } diff --git a/test/fixture/basic/pages/posts.vue b/test/fixture/basic/pages/posts.vue index f1c3f0d73..b3661def0 100644 --- a/test/fixture/basic/pages/posts.vue +++ b/test/fixture/basic/pages/posts.vue @@ -13,6 +13,11 @@ export default { components: { LangSwitcher }, + head () { + return { + title: this.$t('posts') + } + }, nuxtI18n: { paths: { fr: '/articles'