From eacbc761826e83cfa98dc3520808ed8094b260be Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 18 Feb 2022 16:37:27 +0100 Subject: [PATCH] Support trailingSlash in middleware SSR --- packages/next/server/next-server.ts | 15 ++++--- .../react-18-streaming-ssr/index.test.ts | 44 ++++++++++++++++--- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 4c7779481822445..be91e5c625eaf1c 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -8,6 +8,7 @@ import type { ParsedNextUrl } from '../shared/lib/router/utils/parse-next-url' import type { PrerenderManifest } from '../build' import type { Rewrite } from '../lib/load-custom-routes' import type { BaseNextRequest, BaseNextResponse } from './base-http' +import type { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { execOnce } from '../shared/lib/utils' import { @@ -34,7 +35,6 @@ import { CLIENT_PUBLIC_FILES_PATH, SERVERLESS_DIRECTORY, } from '../shared/lib/constants' -import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { recursiveReadDirSync } from './lib/recursive-readdir-sync' import { format as formatUrl, UrlWithParsedQuery } from 'url' import compression from 'next/dist/compiled/compression' @@ -73,6 +73,7 @@ import { loadEnvConfig } from '@next/env' import { getCustomRoute } from './server-route-utils' import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring' import ResponseCache from '../server/response-cache' +import { removePathTrailingSlash } from '../client/normalize-trailing-slash' export * from './base-server' @@ -1029,7 +1030,8 @@ export default class NextNodeServer extends BaseServer { }, }) - if (!this.middleware?.some((m) => m.match(parsedUrl.pathname))) { + const normalizedPathname = removePathTrailingSlash(parsedUrl.pathname) + if (!this.middleware?.some((m) => m.match(normalizedPathname))) { return { finished: false } } @@ -1211,6 +1213,9 @@ export default class NextNodeServer extends BaseServer { onWarning?: (warning: Error) => void }): Promise { this.middlewareBetaWarning() + const normalizedPathname = removePathTrailingSlash( + params.parsedUrl.pathname + ) // For middleware to "fetch" we must always provide an absolute URL const url = getRequestMeta(params.request, '__NEXT_INIT_URL')! @@ -1221,11 +1226,11 @@ export default class NextNodeServer extends BaseServer { } const page: { name?: string; params?: { [key: string]: string } } = {} - if (await this.hasPage(params.parsedUrl.pathname)) { + if (await this.hasPage(normalizedPathname)) { page.name = params.parsedUrl.pathname } else if (this.dynamicRoutes) { for (const dynamicRoute of this.dynamicRoutes) { - const matchParams = dynamicRoute.match(params.parsedUrl.pathname) + const matchParams = dynamicRoute.match(normalizedPathname) if (matchParams) { page.name = dynamicRoute.page page.params = matchParams @@ -1238,7 +1243,7 @@ export default class NextNodeServer extends BaseServer { let result: FetchEventResult | null = null for (const middleware of this.middleware || []) { - if (middleware.match(params.parsedUrl.pathname)) { + if (middleware.match(normalizedPathname)) { if (!(await this.hasMiddleware(middleware.page, middleware.ssr))) { console.warn(`The Edge Function for ${middleware.page} was not found`) continue diff --git a/test/production/react-18-streaming-ssr/index.test.ts b/test/production/react-18-streaming-ssr/index.test.ts index 6f818bc4e08ae0b..7d1114b36a0b080 100644 --- a/test/production/react-18-streaming-ssr/index.test.ts +++ b/test/production/react-18-streaming-ssr/index.test.ts @@ -1,8 +1,8 @@ import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -import { renderViaHTTP } from 'next-test-utils' +import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils' -describe('react-18-streaming-ssr in minimal mode', () => { +describe('react 18 streaming SSR in minimal mode', () => { let next: NextInstance beforeAll(async () => { @@ -27,10 +27,7 @@ describe('react-18-streaming-ssr in minimal mode', () => { react: '18.0.0-rc.0', 'react-dom': '18.0.0-rc.0', }, - skipStart: true, }) - - await next.start() }) afterAll(() => { delete process.env.NEXT_PRIVATE_MINIMAL_MODE @@ -42,3 +39,40 @@ describe('react-18-streaming-ssr in minimal mode', () => { expect(html).toContain('static streaming') }) }) + +describe('react 18 streaming SSR with custom next configs', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/hello.js': ` + export default function Page() { + return

hello

+ } + `, + }, + nextConfig: { + trailingSlash: true, + experimental: { + reactRoot: true, + runtime: 'edge', + }, + }, + dependencies: { + react: '18.0.0-rc.0', + 'react-dom': '18.0.0-rc.0', + }, + }) + }) + afterAll(() => next.destroy()) + + it('should redirect paths without trailing-slash and render when slash is appended', async () => { + const page = '/hello' + const html = await renderViaHTTP(next.url, page + '/') + const res = await fetchViaHTTP(next.url, page, {}, { redirect: 'manual' }) + + expect(html).toContain('hello') + expect(res.status).toBe(308) + }) +})