Skip to content

Commit fa6b503

Browse files
fix: basePath not working properly with admin routes (#14967)
This PR adds a base-path test suite, updates the formatAdminURL helper function. The helper function allows you to create routes inside the admin panel that take `basePath` into account when set. There are a lot of touched files here, most have the same treatment. ## API URL construction changes Previously we were appending the basePath in the default api configuration. That is no longer the case and is more predictable to use the helper function that will generate a correct path/url. ## Admin URL construction changes The default admin route differed from the default api route, the default admin route did not prepend the basePath. The reason it did not/should not, is because `usePathname` from NextJS excludes basePath. So if we were to prepend it to `routes.admin` and then for example use `pathname.startsWith(config.routes.admin)` it would always return false when using a basePath, since `/route` will never start with `/basePath/route`. Also, when you do something like `router.push(/adminRoute)` NextJS will prepend the basePath and push you to `/basePath/adminRoute`. If we prepended it in the config it would push you to `/basePath/basePath/adminRoute`. ## Helper function usage The helper is especially useful for plugin work, this way your plugin will generate correct urls for users using basePath. Before we were doing: ```ts // worked because we prepended basePath to apiRoute const apiURL = `${serverURL}${apiRoute}/collections/${slug}/${id}` // never worked since basePath was never prepended to adminRoute const adminURL = `${serverURL}${adminRoute}/collections/${slug}/${id}` ``` Now we can do: ```ts import { formatAdminURL } from 'payload/shared' // Admin url example const adminURL = formatAdminURL({ adminRoute: config.routes.admin, path: `/collections/${slug}/${id}`, }) // API url example const apiURL = formatAdminURL({ apiRoute: config.routes.api, path: `/collections/${slug}/${id}`, // serverURL, when supplied returns a full url }) ``` If you pass `serverURL` the result will be a full URL. If excluded the result will be a relative path. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3025377 commit fa6b503

169 files changed

Lines changed: 1576 additions & 905 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/post-release-templates.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
- name: Start PostgreSQL
6363
uses: CasperWA/postgresql-action@v1.2
6464
with:
65-
postgresql version: "14" # See https://hub.docker.com/_/postgres for available versions
65+
postgresql version: '14' # See https://hub.docker.com/_/postgres for available versions
6666
postgresql db: ${{ env.POSTGRES_DB }}
6767
postgresql user: ${{ env.POSTGRES_USER }}
6868
postgresql password: ${{ env.POSTGRES_PASSWORD }}
@@ -108,13 +108,13 @@ jobs:
108108
uses: peter-evans/create-pull-request@v7
109109
with:
110110
token: ${{ secrets.GH_TOKEN_POST_RELEASE_TEMPLATES }}
111-
labels: "area: templates"
111+
labels: 'area: templates'
112112
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
113-
commit-message: "templates: bump templates for ${{ needs.wait_for_release.outputs.release_tag }}"
113+
commit-message: 'templates: bump templates for ${{ needs.wait_for_release.outputs.release_tag }}'
114114
branch: ${{ steps.commit.outputs.branch }}
115115
base: main
116116
assignees: ${{ github.actor }}
117-
title: "templates: bump for ${{ needs.wait_for_release.outputs.release_tag }}"
117+
title: 'templates: bump for ${{ needs.wait_for_release.outputs.release_tag }}'
118118
body: |
119119
🤖 Automated bump of templates for ${{ needs.wait_for_release.outputs.release_tag }}
120120

docs/fields/rich-text.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ Instead, you can invest your time and effort into learning the underlying open-s
1717
caption="Admin Panel screenshot of a Rich Text field"
1818
/>
1919

20-
2120
## Config Options
2221

2322
| Option | Description |

examples/astro/payload/docker-compose.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
version: "3"
1+
version: '3'
22

33
services:
44
payload:
55
image: node:18-alpine
66
ports:
7-
- "3000:3000"
7+
- '3000:3000'
88
volumes:
99
- .:/home/node/app
1010
- node_modules:/home/node/app/node_modules
@@ -20,7 +20,7 @@ services:
2020
mongo:
2121
image: mongo:latest
2222
ports:
23-
- "27017:27017"
23+
- '27017:27017'
2424
command:
2525
- --storageEngine=wiredTiger
2626
volumes:

examples/remix/payload/docker-compose.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
version: "3"
1+
version: '3'
22

33
services:
44
payload:
55
image: node:18-alpine
66
ports:
7-
- "3000:3000"
7+
- '3000:3000'
88
volumes:
99
- .:/home/node/app
1010
- node_modules:/home/node/app/node_modules
@@ -20,7 +20,7 @@ services:
2020
mongo:
2121
image: mongo:latest
2222
ports:
23-
- "27017:27017"
23+
- '27017:27017'
2424
command:
2525
- --storageEngine=wiredTiger
2626
volumes:

next.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const withBundleAnalyzer = bundleAnalyzer({
1414
const config = withBundleAnalyzer(
1515
withPayload(
1616
{
17+
basePath: process.env?.NEXT_BASE_PATH || undefined,
1718
eslint: {
1819
ignoreDuringBuilds: true,
1920
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
"obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +",
107107
"prepare": "husky",
108108
"prepare-run-test-against-prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
109-
"prepare-run-test-against-prod:ci": "rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
109+
"prepare-run-test-against-prod:ci": "rm -rf test/packed && rm -rf test/node_modules && rm -rf app && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd ..",
110110
"publish-prerelease": "pnpm --filter releaser publish-prerelease",
111111
"reinstall": "pnpm clean:all && pnpm install",
112112
"release": "pnpm --filter releaser release --tag latest",

packages/live-preview/src/mergeData.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
import { formatAdminURL } from 'payload/shared'
2+
13
import type { CollectionPopulationRequestHandler } from './types.js'
24

3-
const defaultRequestHandler: CollectionPopulationRequestHandler = ({
4-
apiPath,
5-
data,
6-
endpoint,
7-
serverURL,
8-
}) => {
9-
const url = `${serverURL}${apiPath}/${endpoint}`
5+
const defaultRequestHandler: CollectionPopulationRequestHandler = ({ apiPath, data, endpoint }) => {
6+
const url = formatAdminURL({
7+
apiRoute: apiPath,
8+
path: `/${endpoint}`,
9+
})
1010

1111
return fetch(url, {
1212
body: JSON.stringify(data),

packages/next/src/elements/DocumentHeader/Tabs/Tab/TabLink.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22
import type { SanitizedConfig } from 'payload'
33

4-
import { Button, useConfig } from '@payloadcms/ui'
4+
import { Button } from '@payloadcms/ui'
55
import { useParams, usePathname, useSearchParams } from 'next/navigation.js'
66
import { formatAdminURL } from 'payload/shared'
77
import React from 'react'
@@ -25,7 +25,6 @@ export const DocumentTabLink: React.FC<{
2525
}) => {
2626
const pathname = usePathname()
2727
const params = useParams()
28-
const { config } = useConfig()
2928

3029
const searchParams = useSearchParams()
3130

@@ -37,7 +36,6 @@ export const DocumentTabLink: React.FC<{
3736
let docPath = formatAdminURL({
3837
adminRoute,
3938
path: `/${isCollection ? 'collections' : 'globals'}/${entitySlug}`,
40-
serverURL: config.serverURL,
4139
})
4240

4341
if (isCollection) {

packages/next/src/elements/Nav/index.client.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export const DefaultNavClient: React.FC<{
2828
},
2929
folders,
3030
routes: { admin: adminRoute },
31-
serverURL,
3231
},
3332
} = useConfig()
3433

@@ -37,7 +36,6 @@ export const DefaultNavClient: React.FC<{
3736
const folderURL = formatAdminURL({
3837
adminRoute,
3938
path: foldersRoute,
40-
serverURL,
4139
})
4240

4341
const viewingRootFolderView = pathname.startsWith(folderURL)
@@ -53,12 +51,12 @@ export const DefaultNavClient: React.FC<{
5351
let id: string
5452

5553
if (type === EntityType.collection) {
56-
href = formatAdminURL({ adminRoute, path: `/collections/${slug}`, serverURL })
54+
href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })
5755
id = `nav-${slug}`
5856
}
5957

6058
if (type === EntityType.global) {
61-
href = formatAdminURL({ adminRoute, path: `/globals/${slug}`, serverURL })
59+
href = formatAdminURL({ adminRoute, path: `/globals/${slug}` })
6260
id = `nav-global-${slug}`
6361
}
6462

packages/next/src/routes/graphql/playground.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { renderPlaygroundPage } from 'graphql-playground-html'
22
import { createPayloadRequest, type SanitizedConfig } from 'payload'
3+
import { formatAdminURL } from 'payload/shared'
34

45
export const GET = (config: Promise<SanitizedConfig>) => async (request: Request) => {
56
const req = await createPayloadRequest({
@@ -13,9 +14,13 @@ export const GET = (config: Promise<SanitizedConfig>) => async (request: Request
1314
process.env.NODE_ENV === 'production') ||
1415
process.env.NODE_ENV !== 'production'
1516
) {
17+
const endpoint = formatAdminURL({
18+
apiRoute: req.payload.config.routes.api,
19+
path: req.payload.config.routes.graphQL as `/${string}`,
20+
})
1621
return new Response(
1722
renderPlaygroundPage({
18-
endpoint: `${req.payload.config.routes.api}${req.payload.config.routes.graphQL}`,
23+
endpoint,
1924
settings: {
2025
'request.credentials': 'include',
2126
},

0 commit comments

Comments
 (0)