mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-06 04:22:01 +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": {
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -27,6 +29,7 @@
|
||||
"devDependencies": {
|
||||
"@types/etag": "1.8.0",
|
||||
"@types/fs-extra": "11.0.1",
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/node": "14.18.33",
|
||||
"@types/react": "18.0.26",
|
||||
"jest-junit": "16.0.0",
|
||||
|
||||
@@ -17,9 +17,11 @@ import type {
|
||||
export const writeHandler = async ({
|
||||
outDir,
|
||||
handlerFile,
|
||||
prefix = '',
|
||||
}: {
|
||||
outDir: string;
|
||||
handlerFile: string;
|
||||
prefix?: string;
|
||||
}) => {
|
||||
const { major } = await getNodeVersion(process.cwd());
|
||||
|
||||
@@ -35,6 +37,7 @@ export const writeHandler = async ({
|
||||
minify: true,
|
||||
define: {
|
||||
'process.env.NODE_ENV': "'production'",
|
||||
vercel_pathPrefix: JSON.stringify(prefix),
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function createServerlessFunctions(
|
||||
await ensureDir(functionDir);
|
||||
|
||||
await Promise.all([
|
||||
writeHandler({ outDir: functionDir, handlerFile }),
|
||||
writeHandler({ outDir: functionDir, handlerFile, prefix }),
|
||||
copyFunctionLibs({ functionDir }),
|
||||
copyHTMLFiles({ functionDir }),
|
||||
writeVCConfig({ functionDir }),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import os from 'os';
|
||||
import etag from 'etag';
|
||||
import { parse } from 'url';
|
||||
import { join } from 'path';
|
||||
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 CUR_DATA_PATH = join(__dirname, '.cache/data/datastore');
|
||||
@@ -25,34 +25,14 @@ async function getPageSSRHelpers() {
|
||||
}
|
||||
|
||||
export default async function handler(req, res) {
|
||||
let pageName;
|
||||
const pathname = parse(req.url).pathname || '/';
|
||||
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 = '/';
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
const { pathName, isPageData } = getPageName(req.url, vercel_pathPrefix);
|
||||
|
||||
const [graphqlEngine, { getData, renderHTML, renderPageData }] =
|
||||
await Promise.all([getGraphQLEngine(), getPageSSRHelpers()]);
|
||||
|
||||
const data = await getData({
|
||||
pathName: pageName,
|
||||
pathName,
|
||||
graphqlEngine,
|
||||
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",
|
||||
"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",
|
||||
"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':
|
||||
specifier: 11.0.1
|
||||
version: 11.0.1
|
||||
'@types/jest':
|
||||
specifier: 27.5.1
|
||||
version: 27.5.1
|
||||
'@types/node':
|
||||
specifier: 14.18.33
|
||||
version: 14.18.33
|
||||
|
||||
Reference in New Issue
Block a user