Skip to content

Commit 38c2f89

Browse files
fix(next): omit server url from route matching (#14919)
Follow up to #14907. Fixes #14900. Since #14869, setting a `serverURL` causes some additional admin routing to break, including `/admin/logout`. We need to ensure that all routes are relative before matching them. --------- Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
1 parent bb29c49 commit 38c2f89

File tree

7 files changed

+44
-17
lines changed

7 files changed

+44
-17
lines changed

packages/next/src/views/Root/getRouteData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export const getRouteData = ({
160160
return isPathMatchingRoute({
161161
currentRoute,
162162
exact: true,
163-
path: formatAdminURL({ adminRoute, path: route, serverURL: config.serverURL }),
163+
path: formatAdminURL({ adminRoute, path: route }),
164164
})
165165
})
166166

packages/next/src/views/Root/index.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ export const RootPage = async ({
6363

6464
const params = await paramsPromise
6565

66+
// Intentionally omit `serverURL` to ensure relative path
6667
const currentRoute = formatAdminURL({
6768
adminRoute,
6869
path: Array.isArray(params.segments) ? `/${params.segments.join('/')}` : null,
69-
serverURL: config.serverURL,
7070
})
7171

7272
const segments = Array.isArray(params.segments) ? params.segments : []
@@ -140,10 +140,7 @@ export const RootPage = async ({
140140
}),
141141
},
142142
// intentionally omit `serverURL` to keep URL relative
143-
urlSuffix: `${formatAdminURL({
144-
adminRoute,
145-
path: Array.isArray(params.segments) ? `/${params.segments.join('/')}` : null,
146-
})}${searchParams ? queryString : ''}`,
143+
urlSuffix: `${currentRoute}${searchParams ? queryString : ''}`,
147144
},
148145
})
149146

@@ -224,15 +221,11 @@ export const RootPage = async ({
224221
}
225222
}
226223

227-
const createFirstUserRoute = formatAdminURL({
228-
adminRoute,
229-
path: _createFirstUserRoute,
230-
serverURL: config.serverURL,
231-
})
232-
233224
const usersCollection = config.collections.find(({ slug }) => slug === userSlug)
234225
const disableLocalStrategy = usersCollection?.auth?.disableLocalStrategy
235226

227+
const createFirstUserRoute = formatAdminURL({ adminRoute, path: _createFirstUserRoute })
228+
236229
if (disableLocalStrategy && currentRoute === createFirstUserRoute) {
237230
redirect(adminRoute)
238231
}

packages/next/src/views/Root/isPathMatchingRoute.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const isPathMatchingRoute = ({
3333
if (exact) {
3434
return currentRoute === viewRoute
3535
}
36+
3637
if (!exact) {
3738
return viewRoute.startsWith(currentRoute)
3839
}

test/helpers/e2e/auth/logout.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,23 @@ import type { Page } from 'playwright'
33
import { POLL_TOPASS_TIMEOUT } from 'playwright.config.js'
44
import { expect } from 'playwright/test'
55

6+
import { openNav } from '../toggleNav.js'
7+
68
export const logout = async (page: Page, serverURL: string) => {
79
await page.goto(`${serverURL}/admin/logout`)
810

911
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('/admin/login')
1012

1113
await expect(page.locator('.login')).toBeVisible()
1214
}
15+
16+
export const logoutViaNav = async (page: Page) => {
17+
const { nav } = await openNav(page)
18+
const logoutAnchor = nav.locator('a[title="Log out"]')
19+
await expect(logoutAnchor).toBeVisible()
20+
await logoutAnchor.click()
21+
22+
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('/admin/login')
23+
24+
await expect(page.locator('.login')).toBeVisible()
25+
}

test/helpers/e2e/toggleNav.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,36 @@ import type { Page } from '@playwright/test'
22

33
import { expect } from '@playwright/test'
44

5-
export async function openNav(page: Page): Promise<void> {
5+
export async function openNav(page: Page): Promise<{ nav: ReturnType<Page['locator']> }> {
66
// wait for the preferences/media queries to either open or close the nav
77
await expect(page.locator('.template-default--nav-hydrated')).toBeVisible()
88

99
// close all open modals
1010
const dialogs = await page.locator('dialog[open]').elementHandles()
11+
1112
for (let i = 0; i < dialogs.length; i++) {
1213
await page.keyboard.press('Escape')
1314
}
1415

1516
// check to see if the nav is already open and if not, open it
1617
// use the `--nav-open` modifier class to check if the nav is open
1718
// this will prevent clicking nav links that are bleeding off the screen
18-
if (await page.locator('.template-default.template-default--nav-open').isVisible()) {
19-
return
19+
const nav = page.locator('.template-default.template-default--nav-open')
20+
21+
if (await nav.isVisible()) {
22+
return {
23+
nav,
24+
}
2025
}
2126

2227
// playwright: get first element with .nav-toggler which is VISIBLE (not hidden), could be 2 elements with .nav-toggler on mobile and desktop but only one is visible
2328
await page.locator('.nav-toggler >> visible=true').click()
2429
await expect(page.locator('.nav--nav-animate[inert], .nav--nav-hydrated[inert]')).toBeHidden()
2530
await expect(page.locator('.template-default.template-default--nav-open')).toBeVisible()
31+
32+
return {
33+
nav,
34+
}
2635
}
2736

2837
export async function closeNav(page: Page): Promise<void> {

test/server-url/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const dirname = path.dirname(filename)
1111
export default buildConfigWithDefaults({
1212
serverURL: 'http://localhost:3000',
1313
admin: {
14+
autoLogin: false,
1415
importMap: {
1516
baseDir: path.resolve(dirname),
1617
},

test/server-url/e2e.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { Page } from '@playwright/test'
22

33
import { expect, test } from '@playwright/test'
4+
import { login } from 'helpers/e2e/auth/login.js'
5+
import { logoutViaNav } from 'helpers/e2e/auth/logout.js'
6+
import { openNav } from 'helpers/e2e/toggleNav.js'
47
import * as path from 'path'
58
import { fileURLToPath } from 'url'
69

@@ -28,8 +31,15 @@ test.describe('serverURL', () => {
2831
await ensureCompilationIsDone({ page, serverURL })
2932
})
3033

31-
test('can load admin panel when serverURL is set', async () => {
34+
test('can load admin panel', async () => {
35+
await login({ page, serverURL: url.serverURL })
3236
await page.goto(url.admin)
33-
await expect(page.getByText('Dashboard')).toBeVisible()
37+
await expect(page.locator('.dashboard')).toBeVisible()
38+
})
39+
40+
test('can log out', async () => {
41+
await page.goto(url.admin)
42+
await logoutViaNav(page)
43+
await expect(page.locator('.login')).toBeVisible()
3444
})
3545
})

0 commit comments

Comments
 (0)