mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-06 12:57:46 +00:00
[gatsby-plugin-vercel-builder] Fix nested SSR routes (#10751)
Fixes SSR / DSG pages that are nested deeper than the root path for Gatsby projects. Also introduces unit tests for the logic related to determining which page name to use.
This commit is contained in:
5
.changeset/happy-wolves-exist.md
Normal file
5
.changeset/happy-wolves-exist.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@vercel/gatsby-plugin-vercel-builder': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix nested SSR routes
|
||||||
5
packages/gatsby-plugin-vercel-builder/jest.config.js
Normal file
5
packages/gatsby-plugin-vercel-builder/jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
};
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ../../utils/build-builder.mjs",
|
"build": "node ../../utils/build-builder.mjs",
|
||||||
|
"test": "jest --reporters=default --reporters=jest-junit --env node --verbose --bail --runInBand",
|
||||||
|
"test-unit": "pnpm test test/unit.*test.*",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -27,6 +29,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/etag": "1.8.0",
|
"@types/etag": "1.8.0",
|
||||||
"@types/fs-extra": "11.0.1",
|
"@types/fs-extra": "11.0.1",
|
||||||
|
"@types/jest": "27.5.1",
|
||||||
"@types/node": "14.18.33",
|
"@types/node": "14.18.33",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"jest-junit": "16.0.0",
|
"jest-junit": "16.0.0",
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ import type {
|
|||||||
export const writeHandler = async ({
|
export const writeHandler = async ({
|
||||||
outDir,
|
outDir,
|
||||||
handlerFile,
|
handlerFile,
|
||||||
|
prefix = '',
|
||||||
}: {
|
}: {
|
||||||
outDir: string;
|
outDir: string;
|
||||||
handlerFile: string;
|
handlerFile: string;
|
||||||
|
prefix?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { major } = await getNodeVersion(process.cwd());
|
const { major } = await getNodeVersion(process.cwd());
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@ export const writeHandler = async ({
|
|||||||
minify: true,
|
minify: true,
|
||||||
define: {
|
define: {
|
||||||
'process.env.NODE_ENV': "'production'",
|
'process.env.NODE_ENV': "'production'",
|
||||||
|
vercel_pathPrefix: JSON.stringify(prefix),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export async function createServerlessFunctions(
|
|||||||
await ensureDir(functionDir);
|
await ensureDir(functionDir);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
writeHandler({ outDir: functionDir, handlerFile }),
|
writeHandler({ outDir: functionDir, handlerFile, prefix }),
|
||||||
copyFunctionLibs({ functionDir }),
|
copyFunctionLibs({ functionDir }),
|
||||||
copyHTMLFiles({ functionDir }),
|
copyHTMLFiles({ functionDir }),
|
||||||
writeVCConfig({ functionDir }),
|
writeVCConfig({ functionDir }),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import etag from 'etag';
|
import etag from 'etag';
|
||||||
import { parse } from 'url';
|
import { join } from 'path';
|
||||||
import { copySync, existsSync } from 'fs-extra';
|
import { copySync, existsSync } from 'fs-extra';
|
||||||
import { join, dirname, basename } from 'path';
|
import { getPageName } from './utils';
|
||||||
|
|
||||||
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
|
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
|
||||||
const CUR_DATA_PATH = join(__dirname, '.cache/data/datastore');
|
const CUR_DATA_PATH = join(__dirname, '.cache/data/datastore');
|
||||||
@@ -25,34 +25,14 @@ async function getPageSSRHelpers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
let pageName;
|
// eslint-disable-next-line no-undef
|
||||||
const pathname = parse(req.url).pathname || '/';
|
const { pathName, isPageData } = getPageName(req.url, vercel_pathPrefix);
|
||||||
const isPageData = pathname.startsWith('/page-data/');
|
|
||||||
if (isPageData) {
|
|
||||||
// /page-data/index/page-data.json
|
|
||||||
// /page-data/using-ssr/page-data.json
|
|
||||||
pageName = basename(dirname(pathname));
|
|
||||||
if (pageName === 'index') {
|
|
||||||
pageName = '/';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// /using-ssr
|
|
||||||
// /using-ssr/
|
|
||||||
// /using-ssr/index.html
|
|
||||||
pageName = basename(pathname);
|
|
||||||
if (pageName === 'index.html') {
|
|
||||||
pageName = basename(dirname(pathname));
|
|
||||||
}
|
|
||||||
if (!pageName) {
|
|
||||||
pageName = '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [graphqlEngine, { getData, renderHTML, renderPageData }] =
|
const [graphqlEngine, { getData, renderHTML, renderPageData }] =
|
||||||
await Promise.all([getGraphQLEngine(), getPageSSRHelpers()]);
|
await Promise.all([getGraphQLEngine(), getPageSSRHelpers()]);
|
||||||
|
|
||||||
const data = await getData({
|
const data = await getData({
|
||||||
pathName: pageName,
|
pathName,
|
||||||
graphqlEngine,
|
graphqlEngine,
|
||||||
req,
|
req,
|
||||||
});
|
});
|
||||||
|
|||||||
29
packages/gatsby-plugin-vercel-builder/templates/utils.ts
Normal file
29
packages/gatsby-plugin-vercel-builder/templates/utils.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { parse } from 'url';
|
||||||
|
import { basename, dirname } from 'path';
|
||||||
|
|
||||||
|
export function getPageName(url: string, pathPrefix = '') {
|
||||||
|
let pathName = (parse(url).pathname || '/').slice(pathPrefix.length);
|
||||||
|
const isPageData = pathName.startsWith('/page-data/');
|
||||||
|
if (isPageData) {
|
||||||
|
// "/page-data/index/page-data.json" -> "/"
|
||||||
|
// "/page-data/using-ssr/page-data.json" -> "using-ssr"
|
||||||
|
// "/page-data/foo/bar/ssr/page-data.json" -> "foo/bar/ssr"
|
||||||
|
pathName = pathName.split('/').slice(2, -1).join('/');
|
||||||
|
if (pathName === 'index') {
|
||||||
|
pathName = '/';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// "/using-ssr" -> "using-ssr"
|
||||||
|
// "/using-ssr/" -> "using-ssr"
|
||||||
|
// "/using-ssr/index.html" -> "using-ssr"
|
||||||
|
// "/foo/bar/ssr" -> "foo/bar/ssr"
|
||||||
|
if (basename(pathName) === 'index.html') {
|
||||||
|
pathName = dirname(pathName);
|
||||||
|
}
|
||||||
|
if (pathName !== '/') {
|
||||||
|
// Remove leading and trailing "/"
|
||||||
|
pathName = pathName.replace(/(^\/|\/$)/g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { isPageData, pathName };
|
||||||
|
}
|
||||||
7
packages/gatsby-plugin-vercel-builder/test/tsconfig.json
vendored
Normal file
7
packages/gatsby-plugin-vercel-builder/test/tsconfig.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": ["*.test.ts"]
|
||||||
|
}
|
||||||
64
packages/gatsby-plugin-vercel-builder/test/unit.get-page-name.test.ts
vendored
Normal file
64
packages/gatsby-plugin-vercel-builder/test/unit.get-page-name.test.ts
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { getPageName } from '../templates/utils';
|
||||||
|
|
||||||
|
describe('getPageName()', () => {
|
||||||
|
it.each([
|
||||||
|
{
|
||||||
|
input: '/page-data/index/page-data.json',
|
||||||
|
pageName: '/',
|
||||||
|
isPageData: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '/page-data/using-ssr/page-data.json',
|
||||||
|
pageName: 'using-ssr',
|
||||||
|
isPageData: true,
|
||||||
|
},
|
||||||
|
{ input: '/', pageName: '/', isPageData: false },
|
||||||
|
{ input: '/index.html', pageName: '/', isPageData: false },
|
||||||
|
{ input: '/using-ssr', pageName: 'using-ssr', isPageData: false },
|
||||||
|
{ input: '/using-ssr/', pageName: 'using-ssr', isPageData: false },
|
||||||
|
{
|
||||||
|
input: '/using-ssr/index.html',
|
||||||
|
pageName: 'using-ssr',
|
||||||
|
isPageData: false,
|
||||||
|
},
|
||||||
|
{ input: '/foo/bar/ssr', pageName: 'foo/bar/ssr', isPageData: false },
|
||||||
|
{
|
||||||
|
input: '/page-data/foo/bar/ssr/page-data.json',
|
||||||
|
pageName: 'foo/bar/ssr',
|
||||||
|
isPageData: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ input: '/foo/', pathPrefix: '/foo', pageName: '/', isPageData: false },
|
||||||
|
{
|
||||||
|
input: '/foo/index.html',
|
||||||
|
pathPrefix: '/foo',
|
||||||
|
pageName: '/',
|
||||||
|
isPageData: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '/foo/bar/ssr',
|
||||||
|
pathPrefix: '/foo/',
|
||||||
|
pageName: 'bar/ssr',
|
||||||
|
isPageData: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '/foo/page-data/index/page-data.json',
|
||||||
|
pathPrefix: '/foo',
|
||||||
|
pageName: '/',
|
||||||
|
isPageData: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '/foo/page-data/bar/ssr/page-data.json',
|
||||||
|
pathPrefix: '/foo',
|
||||||
|
pageName: 'bar/ssr',
|
||||||
|
isPageData: true,
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
'Should return "$pageName" for "$input"',
|
||||||
|
({ input, pathPrefix, pageName, isPageData }) => {
|
||||||
|
const actual = getPageName(input, pathPrefix);
|
||||||
|
expect(actual.pathName).toEqual(pageName);
|
||||||
|
expect(actual.isPageData).toEqual(isPageData);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -12,6 +12,10 @@
|
|||||||
{
|
{
|
||||||
"path": "/foo/x/y/z",
|
"path": "/foo/x/y/z",
|
||||||
"mustContain": "<h1>Page not found</h1>"
|
"mustContain": "<h1>Page not found</h1>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/foo/foo/bar/ssr/",
|
||||||
|
"mustContain": "<h1>This page is <!-- -->rendered server side (nested)</h1>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
21
packages/static-build/test/fixtures/gatsby-v5-pathPrefix/src/pages/foo/bar/ssr.js
vendored
Normal file
21
packages/static-build/test/fixtures/gatsby-v5-pathPrefix/src/pages/foo/bar/ssr.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const UsingSSR = ({ serverData }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>
|
||||||
|
This page is {serverData.message}
|
||||||
|
</h1>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Head = () => <title>SSR Gatsby</title>;
|
||||||
|
|
||||||
|
export default UsingSSR;
|
||||||
|
|
||||||
|
export async function getServerData() {
|
||||||
|
return {
|
||||||
|
props: { message: 'rendered server side (nested)' },
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,10 @@
|
|||||||
{
|
{
|
||||||
"path": "/x/y/z",
|
"path": "/x/y/z",
|
||||||
"mustContain": "<h1>Page not found</h1>"
|
"mustContain": "<h1>Page not found</h1>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/foo/bar/ssr/",
|
||||||
|
"mustContain": "<h1>This page is <!-- -->rendered server side (nested)</h1>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
21
packages/static-build/test/fixtures/gatsby-v5/src/pages/foo/bar/ssr.js
vendored
Normal file
21
packages/static-build/test/fixtures/gatsby-v5/src/pages/foo/bar/ssr.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const UsingSSR = ({ serverData }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>
|
||||||
|
This page is {serverData.message}
|
||||||
|
</h1>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Head = () => <title>SSR Gatsby</title>;
|
||||||
|
|
||||||
|
export default UsingSSR;
|
||||||
|
|
||||||
|
export async function getServerData() {
|
||||||
|
return {
|
||||||
|
props: { message: 'rendered server side (nested)' },
|
||||||
|
}
|
||||||
|
}
|
||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -969,6 +969,9 @@ importers:
|
|||||||
'@types/fs-extra':
|
'@types/fs-extra':
|
||||||
specifier: 11.0.1
|
specifier: 11.0.1
|
||||||
version: 11.0.1
|
version: 11.0.1
|
||||||
|
'@types/jest':
|
||||||
|
specifier: 27.5.1
|
||||||
|
version: 27.5.1
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 14.18.33
|
specifier: 14.18.33
|
||||||
version: 14.18.33
|
version: 14.18.33
|
||||||
|
|||||||
Reference in New Issue
Block a user