diff --git a/e2e-tests/development-runtime/cypress/integration/ssr.js b/e2e-tests/development-runtime/cypress/integration/ssr.js new file mode 100644 index 0000000000000..00dc2236c4fd4 --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/ssr.js @@ -0,0 +1,108 @@ +const staticPath = `/ssr/static-path/` +const paramPath = `/ssr/param-path/` +const wildcardPath = `/ssr/wildcard-path/` + +describe(`Static path ('${staticPath}')`, () => { + it(`Direct visit no query params`, () => { + cy.visit(staticPath).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{}`) + }) + + it(`Direct visit with query params`, () => { + cy.visit(staticPath + `?foo=bar`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{}`) + }) + + it(`Client navigation to same path with different query params`, () => { + cy.visit(staticPath).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{}`) + cy.window() + .then(win => win.___navigate(staticPath + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{}`) + cy.window() + .then(win => win.___navigate(staticPath + `?foo=baz`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"baz"}`) + cy.getTestElement(`params`).contains(`{}`) + cy.window() + .then(win => win.___navigate(staticPath)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{}`) + }) +}) + +describe(`Param path ('${paramPath}:param')`, () => { + it(`Direct visit no query params`, () => { + cy.visit(paramPath + `foo/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + }) + + it(`Direct visit with query params`, () => { + cy.visit(paramPath + `foo/` + `?foo=bar`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + }) + + it(`Client navigation to same param path with different query params and url params`, () => { + cy.visit(paramPath + `foo/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + cy.window() + .then(win => win.___navigate(paramPath + `foo/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + cy.window() + .then(win => win.___navigate(paramPath + `baz/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"param":"baz"}`) + cy.window() + .then(win => win.___navigate(paramPath + `baz/`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"param":"baz"}`) + }) +}) + +describe(`Wildcard path ('${wildcardPath}*')`, () => { + it(`Direct visit no query params`, () => { + cy.visit(wildcardPath + `foo/nested/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo/nested"}`) + }) + + it(`Direct visit with query params`, () => { + cy.visit(wildcardPath + `foo/nested/` + `?foo=bar`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo/nested"}`) + }) + + it(`Client navigation to same param path with different query params and url params`, () => { + cy.visit(wildcardPath + `foo/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo"}`) + cy.window() + .then(win => win.___navigate(wildcardPath + `foo/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo"}`) + cy.window() + .then(win => win.___navigate(wildcardPath + `baz/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"wildcard":"baz"}`) + cy.window() + .then(win => win.___navigate(wildcardPath + `baz/`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"wildcard":"baz"}`) + }) +}) diff --git a/e2e-tests/development-runtime/src/pages/ssr/param-path/[param].js b/e2e-tests/development-runtime/src/pages/ssr/param-path/[param].js new file mode 100644 index 0000000000000..af860ab139565 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/ssr/param-path/[param].js @@ -0,0 +1,22 @@ +import React from "react" + +export default function Params({ serverData }) { + return ( +
+

Query

+
{JSON.stringify(serverData?.arg?.query)}
+

Params

+
{JSON.stringify(serverData?.arg?.params)}
+

Debug

+
{JSON.stringify({ serverData }, null, 2)}
+
+ ) +} + +export function getServerData(arg) { + return { + props: { + arg, + }, + } +} diff --git a/e2e-tests/development-runtime/src/pages/ssr/static-path.js b/e2e-tests/development-runtime/src/pages/ssr/static-path.js new file mode 100644 index 0000000000000..ed3265bef789f --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/ssr/static-path.js @@ -0,0 +1,22 @@ +import React from "react" + +export default function StaticPath({ serverData }) { + return ( +
+

Query

+
{JSON.stringify(serverData?.arg?.query)}
+

Params

+
{JSON.stringify(serverData?.arg?.params)}
+

Debug

+
{JSON.stringify({ serverData }, null, 2)}
+
+ ) +} + +export function getServerData(arg) { + return { + props: { + arg, + }, + } +} diff --git a/e2e-tests/development-runtime/src/pages/ssr/wildcard-path/[...wildcard].js b/e2e-tests/development-runtime/src/pages/ssr/wildcard-path/[...wildcard].js new file mode 100644 index 0000000000000..9af5b2c9e2f71 --- /dev/null +++ b/e2e-tests/development-runtime/src/pages/ssr/wildcard-path/[...wildcard].js @@ -0,0 +1,22 @@ +import React from "react" + +export default function Wildcard({ serverData }) { + return ( +
+

Query

+
{JSON.stringify(serverData?.arg?.query)}
+

Params

+
{JSON.stringify(serverData?.arg?.params)}
+

Debug

+
{JSON.stringify({ serverData }, null, 2)}
+
+ ) +} + +export function getServerData(arg) { + return { + props: { + arg, + }, + } +} diff --git a/e2e-tests/production-runtime/cypress/integration/redirects.js b/e2e-tests/production-runtime/cypress/integration/redirects.js index 49229e27fa8dc..82e83350723ca 100644 --- a/e2e-tests/production-runtime/cypress/integration/redirects.js +++ b/e2e-tests/production-runtime/cypress/integration/redirects.js @@ -75,7 +75,7 @@ describe(`Redirects`, () => { failOnStatusCode: false, }).waitForRouteChange() - cy.location(`pathname`).should(`equal`, `/redirect-search-hash/`) + cy.location(`pathname`).should(`equal`, `/redirect-search-hash`) cy.location(`hash`).should(`equal`, `#anchor`) cy.location(`search`).should(`equal`, ``) }) @@ -96,7 +96,7 @@ describe(`Redirects`, () => { failOnStatusCode: false, }).waitForRouteChange() - cy.location(`pathname`).should(`equal`, `/redirect-search-hash/`) + cy.location(`pathname`).should(`equal`, `/redirect-search-hash`) cy.location(`hash`).should(`equal`, ``) cy.location(`search`).should(`equal`, `?query_param=hello`) }) @@ -117,7 +117,7 @@ describe(`Redirects`, () => { failOnStatusCode: false, }).waitForRouteChange() - cy.location(`pathname`).should(`equal`, `/redirect-search-hash/`) + cy.location(`pathname`).should(`equal`, `/redirect-search-hash`) cy.location(`hash`).should(`equal`, `#anchor`) cy.location(`search`).should(`equal`, `?query_param=hello`) }) diff --git a/e2e-tests/production-runtime/cypress/integration/ssr.js b/e2e-tests/production-runtime/cypress/integration/ssr.js new file mode 100644 index 0000000000000..00dc2236c4fd4 --- /dev/null +++ b/e2e-tests/production-runtime/cypress/integration/ssr.js @@ -0,0 +1,108 @@ +const staticPath = `/ssr/static-path/` +const paramPath = `/ssr/param-path/` +const wildcardPath = `/ssr/wildcard-path/` + +describe(`Static path ('${staticPath}')`, () => { + it(`Direct visit no query params`, () => { + cy.visit(staticPath).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{}`) + }) + + it(`Direct visit with query params`, () => { + cy.visit(staticPath + `?foo=bar`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{}`) + }) + + it(`Client navigation to same path with different query params`, () => { + cy.visit(staticPath).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{}`) + cy.window() + .then(win => win.___navigate(staticPath + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{}`) + cy.window() + .then(win => win.___navigate(staticPath + `?foo=baz`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"baz"}`) + cy.getTestElement(`params`).contains(`{}`) + cy.window() + .then(win => win.___navigate(staticPath)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{}`) + }) +}) + +describe(`Param path ('${paramPath}:param')`, () => { + it(`Direct visit no query params`, () => { + cy.visit(paramPath + `foo/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + }) + + it(`Direct visit with query params`, () => { + cy.visit(paramPath + `foo/` + `?foo=bar`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + }) + + it(`Client navigation to same param path with different query params and url params`, () => { + cy.visit(paramPath + `foo/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + cy.window() + .then(win => win.___navigate(paramPath + `foo/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"param":"foo"}`) + cy.window() + .then(win => win.___navigate(paramPath + `baz/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"param":"baz"}`) + cy.window() + .then(win => win.___navigate(paramPath + `baz/`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"param":"baz"}`) + }) +}) + +describe(`Wildcard path ('${wildcardPath}*')`, () => { + it(`Direct visit no query params`, () => { + cy.visit(wildcardPath + `foo/nested/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo/nested"}`) + }) + + it(`Direct visit with query params`, () => { + cy.visit(wildcardPath + `foo/nested/` + `?foo=bar`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo/nested"}`) + }) + + it(`Client navigation to same param path with different query params and url params`, () => { + cy.visit(wildcardPath + `foo/`).waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo"}`) + cy.window() + .then(win => win.___navigate(wildcardPath + `foo/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"wildcard":"foo"}`) + cy.window() + .then(win => win.___navigate(wildcardPath + `baz/` + `?foo=bar`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{"foo":"bar"}`) + cy.getTestElement(`params`).contains(`{"wildcard":"baz"}`) + cy.window() + .then(win => win.___navigate(wildcardPath + `baz/`)) + .waitForRouteChange() + cy.getTestElement(`query`).contains(`{}`) + cy.getTestElement(`params`).contains(`{"wildcard":"baz"}`) + }) +}) diff --git a/e2e-tests/production-runtime/src/pages/ssr/param-path/[param].js b/e2e-tests/production-runtime/src/pages/ssr/param-path/[param].js new file mode 100644 index 0000000000000..af860ab139565 --- /dev/null +++ b/e2e-tests/production-runtime/src/pages/ssr/param-path/[param].js @@ -0,0 +1,22 @@ +import React from "react" + +export default function Params({ serverData }) { + return ( +
+

Query

+
{JSON.stringify(serverData?.arg?.query)}
+

Params

+
{JSON.stringify(serverData?.arg?.params)}
+

Debug

+
{JSON.stringify({ serverData }, null, 2)}
+
+ ) +} + +export function getServerData(arg) { + return { + props: { + arg, + }, + } +} diff --git a/e2e-tests/production-runtime/src/pages/ssr/static-path.js b/e2e-tests/production-runtime/src/pages/ssr/static-path.js new file mode 100644 index 0000000000000..ed3265bef789f --- /dev/null +++ b/e2e-tests/production-runtime/src/pages/ssr/static-path.js @@ -0,0 +1,22 @@ +import React from "react" + +export default function StaticPath({ serverData }) { + return ( +
+

Query

+
{JSON.stringify(serverData?.arg?.query)}
+

Params

+
{JSON.stringify(serverData?.arg?.params)}
+

Debug

+
{JSON.stringify({ serverData }, null, 2)}
+
+ ) +} + +export function getServerData(arg) { + return { + props: { + arg, + }, + } +} diff --git a/e2e-tests/production-runtime/src/pages/ssr/wildcard-path/[...wildcard].js b/e2e-tests/production-runtime/src/pages/ssr/wildcard-path/[...wildcard].js new file mode 100644 index 0000000000000..9af5b2c9e2f71 --- /dev/null +++ b/e2e-tests/production-runtime/src/pages/ssr/wildcard-path/[...wildcard].js @@ -0,0 +1,22 @@ +import React from "react" + +export default function Wildcard({ serverData }) { + return ( +
+

Query

+
{JSON.stringify(serverData?.arg?.query)}
+

Params

+
{JSON.stringify(serverData?.arg?.params)}
+

Debug

+
{JSON.stringify({ serverData }, null, 2)}
+
+ ) +} + +export function getServerData(arg) { + return { + props: { + arg, + }, + } +} diff --git a/packages/gatsby-link/src/index.js b/packages/gatsby-link/src/index.js index 47f6fc2e77767..c1abef043f2bb 100644 --- a/packages/gatsby-link/src/index.js +++ b/packages/gatsby-link/src/index.js @@ -117,17 +117,19 @@ class GatsbyLink extends React.Component { } _prefetch() { - let currentPath = window.location.pathname + let currentPath = window.location.pathname + window.location.search // reach router should have the correct state if (this.props._location && this.props._location.pathname) { - currentPath = this.props._location.pathname + currentPath = this.props._location.pathname + this.props._location.search } const rewrittenPath = rewriteLinkPath(this.props.to, currentPath) - const newPathName = parsePath(rewrittenPath).pathname + const parsed = parsePath(rewrittenPath) - // Prefech is used to speed up next navigations. When you use it on the current navigation, + const newPathName = parsed.pathname + parsed.search + + // Prefetch is used to speed up next navigations. When you use it on the current navigation, // there could be a race-condition where Chrome uses the stale data instead of waiting for the network to complete if (currentPath !== newPathName) { ___loader.enqueue(newPathName) @@ -224,7 +226,8 @@ class GatsbyLink extends React.Component { if (onMouseEnter) { onMouseEnter(e) } - ___loader.hovering(parsePath(prefixedTo).pathname) + const parsed = parsePath(prefixedTo) + ___loader.hovering(parsed.pathname + parsed.search) }} onClick={e => { if (onClick) { diff --git a/packages/gatsby/cache-dir/__tests__/ensure-resources.tsx b/packages/gatsby/cache-dir/__tests__/ensure-resources.tsx index 8d5841c614fbd..472617ee2b983 100644 --- a/packages/gatsby/cache-dir/__tests__/ensure-resources.tsx +++ b/packages/gatsby/cache-dir/__tests__/ensure-resources.tsx @@ -22,6 +22,7 @@ describe(`EnsureResources`, () => { it(`loads pages synchronously`, () => { const location = { pathname: `/`, + search: ``, } const { container } = render( diff --git a/packages/gatsby/cache-dir/app.js b/packages/gatsby/cache-dir/app.js index dfd7cae909877..45f439e57bfd7 100644 --- a/packages/gatsby/cache-dir/app.js +++ b/packages/gatsby/cache-dir/app.js @@ -174,7 +174,7 @@ apiRunnerAsync(`onClientEntry`).then(() => { Promise.all([ loader.loadPage(`/dev-404-page/`), loader.loadPage(`/404.html`), - loader.loadPage(window.location.pathname), + loader.loadPage(window.location.pathname + window.location.search), ]).then(() => { navigationInit() diff --git a/packages/gatsby/cache-dir/ensure-resources.js b/packages/gatsby/cache-dir/ensure-resources.js index 80bbb8d8dcdf9..b1880c92b643b 100644 --- a/packages/gatsby/cache-dir/ensure-resources.js +++ b/packages/gatsby/cache-dir/ensure-resources.js @@ -10,15 +10,20 @@ class EnsureResources extends React.Component { location: { ...location }, pageResources: pageResources || - loader.loadPageSync(location.pathname, { withErrorDetails: true }), + loader.loadPageSync(location.pathname + location.search, { + withErrorDetails: true, + }), } } static getDerivedStateFromProps({ location }, prevState) { if (prevState.location.href !== location.href) { - const pageResources = loader.loadPageSync(location.pathname, { - withErrorDetails: true, - }) + const pageResources = loader.loadPageSync( + location.pathname + location.search, + { + withErrorDetails: true, + } + ) return { pageResources, @@ -48,7 +53,9 @@ class EnsureResources extends React.Component { shouldComponentUpdate(nextProps, nextState) { // Always return false if we're missing resources. if (!nextState.pageResources) { - this.loadResources(nextProps.location.pathname) + this.loadResources( + nextProps.location.pathname + nextProps.location.search + ) return false } @@ -56,7 +63,9 @@ class EnsureResources extends React.Component { process.env.BUILD_STAGE === `develop` && nextState.pageResources.stale ) { - this.loadResources(nextProps.location.pathname) + this.loadResources( + nextProps.location.pathname + nextProps.location.search + ) return false } diff --git a/packages/gatsby/cache-dir/find-path.js b/packages/gatsby/cache-dir/find-path.js index 969ce6a22765b..1b1d149d2051d 100644 --- a/packages/gatsby/cache-dir/find-path.js +++ b/packages/gatsby/cache-dir/find-path.js @@ -15,8 +15,6 @@ const trimPathname = rawPathname => { ) // Remove any hashfragment .split(`#`)[0] - // Remove search query - .split(`?`)[0] return trimmedPathname } diff --git a/packages/gatsby/cache-dir/loader.js b/packages/gatsby/cache-dir/loader.js index 3f08cabca0b56..161205943d08f 100644 --- a/packages/gatsby/cache-dir/loader.js +++ b/packages/gatsby/cache-dir/loader.js @@ -24,9 +24,12 @@ const stripSurroundingSlashes = s => { return s } -const createPageDataUrl = path => { +const createPageDataUrl = rawPath => { + const [path, maybeSearch] = rawPath.split(`?`) const fixedPath = path === `/` ? `index` : stripSurroundingSlashes(path) - return `${__PATH_PREFIX__}/page-data/${fixedPath}/page-data.json` + return `${__PATH_PREFIX__}/page-data/${fixedPath}/page-data.json${ + maybeSearch ? `?${maybeSearch}` : `` + }` } function doFetch(url, method = `GET`) { @@ -141,6 +144,11 @@ export class BaseLoader { throw new Error(`not a valid pageData response`) } + const maybeSearch = pagePath.split(`?`)[1] + if (maybeSearch && !jsonPayload.path.includes(maybeSearch)) { + jsonPayload.path += `?${maybeSearch}` + } + return Object.assign(loadObj, { status: PageResourceStatus.Success, payload: jsonPayload, diff --git a/packages/gatsby/cache-dir/navigation.js b/packages/gatsby/cache-dir/navigation.js index 6a7b1ae3f09c8..9bf8fbbb43679 100644 --- a/packages/gatsby/cache-dir/navigation.js +++ b/packages/gatsby/cache-dir/navigation.js @@ -85,7 +85,7 @@ const navigate = (to, options = {}) => { }) }, 1000) - loader.loadPage(pathname).then(pageResources => { + loader.loadPage(pathname + search).then(pageResources => { // If no page resources, then refresh the page // Do this, rather than simply `window.location.reload()`, so that // pressing the back/forward buttons work - otherwise when pressing @@ -102,10 +102,6 @@ const navigate = (to, options = {}) => { // If the loaded page has a different compilation hash to the // window, then a rebuild has occurred on the server. Reload. if (process.env.NODE_ENV === `production` && pageResources) { - // window.___webpackCompilationHash gets set in production-app.js after navigationInit() is called - // So on a direct visit of a page with a browser redirect this check is truthy and thus the codepath is hit - // While the resource actually exists, but only too late - // TODO: This should probably be fixed by setting ___webpackCompilationHash before navigationInit() is called if ( pageResources.page.webpackCompilationHash !== window.___webpackCompilationHash @@ -172,9 +168,6 @@ function init() { window.___push = to => navigate(to, { replace: false }) window.___replace = to => navigate(to, { replace: true }) window.___navigate = (to, options) => navigate(to, options) - - // Check for initial page-load redirect - maybeRedirect(window.location.pathname) } class RouteAnnouncer extends React.Component { diff --git a/packages/gatsby/cache-dir/normalize-page-path.js b/packages/gatsby/cache-dir/normalize-page-path.js index e3aa70b2f45c5..51dd49a6b82b9 100644 --- a/packages/gatsby/cache-dir/normalize-page-path.js +++ b/packages/gatsby/cache-dir/normalize-page-path.js @@ -1,12 +1,17 @@ -export default path => { - if (path === undefined) { - return path +export default pathAndSearch => { + if (pathAndSearch === undefined) { + return pathAndSearch } + let [path, search = ``] = pathAndSearch.split(`?`) + if (search) { + search = `?` + search + } + if (path === `/`) { - return `/` + return `/` + search } if (path.charAt(path.length - 1) === `/`) { - return path.slice(0, -1) + return path.slice(0, -1) + search } - return path + return path + search } diff --git a/packages/gatsby/cache-dir/production-app.js b/packages/gatsby/cache-dir/production-app.js index 5633e5863a706..225297cf018a2 100644 --- a/packages/gatsby/cache-dir/production-app.js +++ b/packages/gatsby/cache-dir/production-app.js @@ -107,8 +107,10 @@ apiRunnerAsync(`onClientEntry`).then(() => { pageResources.page.path === `/404.html` ? stripPrefix(location.pathname, __BASE_PATH__) : encodeURI( - pageResources.page.matchPath || + ( + pageResources.page.matchPath || pageResources.page.path + ).split(`?`)[0] ) } {...this.props} @@ -128,15 +130,18 @@ apiRunnerAsync(`onClientEntry`).then(() => { const { pagePath, location: browserLoc } = window // Explicitly call navigate if the canonical path (window.pagePath) - // is different to the browser path (window.location.pathname). But - // only if NONE of the following conditions hold: + // is different to the browser path (window.location.pathname). SSR + // page paths might include search params, while SSG and DSG won't. + // If page path include search params we also compare query params. + // But only if NONE of the following conditions hold: // // - The url matches a client side route (page.matchPath) // - it's a 404 page // - it's the offline plugin shell (/offline-plugin-app-shell-fallback/) if ( pagePath && - __BASE_PATH__ + pagePath !== browserLoc.pathname && + __BASE_PATH__ + pagePath !== + browserLoc.pathname + (pagePath.includes(`?`) ? browserLoc.search : ``) && !( loader.findMatchPath(stripPrefix(browserLoc.pathname, __BASE_PATH__)) || pagePath === `/404.html` || @@ -144,12 +149,12 @@ apiRunnerAsync(`onClientEntry`).then(() => { pagePath.match(/^\/offline-plugin-app-shell-fallback\/?$/) ) ) { - navigate(__BASE_PATH__ + pagePath + browserLoc.search + browserLoc.hash, { + navigate(__BASE_PATH__ + pagePath + browserLoc.hash, { replace: true, }) } - publicLoader.loadPage(browserLoc.pathname).then(page => { + publicLoader.loadPage(browserLoc.pathname + browserLoc.search).then(page => { if (!page || page.status === PageResourceStatus.Error) { const message = `page resources for ${browserLoc.pathname} not found. Not rendering React` diff --git a/packages/gatsby/cache-dir/root.js b/packages/gatsby/cache-dir/root.js index fc0b1625c3595..66c034d7641dd 100644 --- a/packages/gatsby/cache-dir/root.js +++ b/packages/gatsby/cache-dir/root.js @@ -32,7 +32,7 @@ class LocationHandler extends React.Component { render() { const { location } = this.props - if (!loader.isPageNotFound(location.pathname)) { + if (!loader.isPageNotFound(location.pathname + location.search)) { return ( {locationAndPageResources => ( @@ -48,8 +48,10 @@ class LocationHandler extends React.Component { > => { } return res.send(results) } - - return res.status(404).sendFile(`404.html`, { root }) } return next() }) diff --git a/packages/gatsby/src/utils/develop-preload-headers.ts b/packages/gatsby/src/utils/develop-preload-headers.ts index 80d251d9e582c..20154270a8d9c 100644 --- a/packages/gatsby/src/utils/develop-preload-headers.ts +++ b/packages/gatsby/src/utils/develop-preload-headers.ts @@ -32,7 +32,7 @@ export async function appendPreloadHeaders( // add page-data.json preload // our runtime also demands 404 and dev-404 page-data to be fetched to even render (see cache-dir/app.js) const pagePathsToPreload = [`/404.html`, `/dev-404-page/`] - if (!pagePathsToPreload.includes(page.path)) { + if (page.mode !== `SSR` && !pagePathsToPreload.includes(page.path)) { // let's make sure page path is first one (order shouldn't matter, just for reasonable order) pagePathsToPreload.unshift(page.path) } diff --git a/packages/gatsby/src/utils/page-ssr-module/entry.ts b/packages/gatsby/src/utils/page-ssr-module/entry.ts index 221244265a970..fad7870043674 100644 --- a/packages/gatsby/src/utils/page-ssr-module/entry.ts +++ b/packages/gatsby/src/utils/page-ssr-module/entry.ts @@ -29,6 +29,7 @@ export interface ISSRData { templateDetails: ITemplateDetails potentialPagePath: string serverDataHeaders?: Record + searchString: string } const pageTemplateDetailsMap: Record< @@ -101,15 +102,34 @@ export async function getData({ } results.pageContext = page.context + let searchString = `` + if (req?.query) { + const maybeQueryString = Object.entries(req.query) + .map(([k, v]) => `${k}=${v}`) + .join(`&`) + if (maybeQueryString) { + searchString = `?${maybeQueryString}` + } + } + return { results, page, templateDetails, potentialPagePath, serverDataHeaders: serverData?.headers, + searchString, } } +function getPath(data: ISSRData): string { + return ( + (data.page.mode !== `SSG` && data.page.matchPath + ? data.potentialPagePath + : data.page.path) + (data.page.mode === `SSR` ? data.searchString : ``) + ) +} + export async function renderPageData({ data, }: { @@ -118,10 +138,7 @@ export async function renderPageData({ const results = await constructPageDataString( { componentChunkName: data.page.componentChunkName, - path: - data.page.mode !== `SSG` && data.page.matchPath - ? data.potentialPagePath - : data.page.path, + path: getPath(data), matchPath: data.page.matchPath, staticQueryHashes: data.templateDetails.staticQueryHashes, }, @@ -161,10 +178,7 @@ export async function renderHTML({ ) const results = await htmlComponentRenderer({ - pagePath: - data.page.mode !== `SSG` && data.page.matchPath - ? data.potentialPagePath - : data.page.path, + pagePath: getPath(data), pageData, staticQueryContext, ...data.templateDetails.assets,