Fix: Only trim /1
from the canonical URL for paginate() routes (#3393)
* only trim /1 from canonical URLs for paginate() routes * chore: fixing eslint warning * chore: add changeset * typo: copy paste error * adding a test validation error message * verifying canonical for all three test routes * TEMP: extra test logging to track down the failure * TEMP: additional test logging to see what the failing CLI messages are * TEMP: digging deeper, it's getting stuck on port 3000 is taken * TEMP: why is it breaking when LOCAL isn't logged? * TEMP: still digging, strange how consistent this failure is * finally found it - the new test wasn't closing the dev server...
This commit is contained in:
parent
1f148dbcfd
commit
d372d29ef8
6 changed files with 84 additions and 6 deletions
5
.changeset/quick-waves-act.md
Normal file
5
.changeset/quick-waves-act.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes a bug in the canonical URL when using `1` as a route parameter in `getStaticPaths()`
|
|
@ -133,6 +133,7 @@ export async function render(
|
||||||
markdown,
|
markdown,
|
||||||
origin,
|
origin,
|
||||||
params,
|
params,
|
||||||
|
props: pageProps,
|
||||||
pathname,
|
pathname,
|
||||||
resolve,
|
resolve,
|
||||||
renderers,
|
renderers,
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { bold } from 'kleur/colors';
|
||||||
import type {
|
import type {
|
||||||
AstroGlobal,
|
AstroGlobal,
|
||||||
AstroGlobalPartial,
|
AstroGlobalPartial,
|
||||||
|
Page,
|
||||||
Params,
|
Params,
|
||||||
|
Props,
|
||||||
SSRElement,
|
SSRElement,
|
||||||
SSRLoadedRenderer,
|
SSRLoadedRenderer,
|
||||||
SSRResult,
|
SSRResult,
|
||||||
|
@ -27,6 +29,7 @@ export interface CreateResultArgs {
|
||||||
markdown: MarkdownRenderingOptions;
|
markdown: MarkdownRenderingOptions;
|
||||||
params: Params;
|
params: Params;
|
||||||
pathname: string;
|
pathname: string;
|
||||||
|
props: Props;
|
||||||
renderers: SSRLoadedRenderer[];
|
renderers: SSRLoadedRenderer[];
|
||||||
resolve: (s: string) => Promise<string>;
|
resolve: (s: string) => Promise<string>;
|
||||||
site: string | undefined;
|
site: string | undefined;
|
||||||
|
@ -99,11 +102,16 @@ class Slots {
|
||||||
|
|
||||||
let renderMarkdown: any = null;
|
let renderMarkdown: any = null;
|
||||||
|
|
||||||
export function createResult(args: CreateResultArgs): SSRResult {
|
function isPaginatedRoute({ page }: { page?: Page }) {
|
||||||
const { markdown, params, pathname, renderers, request, resolve, site } = args;
|
return page && 'currentPage' in page;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
|
const { markdown, params, pathname, props: pageProps, renderers, request, resolve, site } = args;
|
||||||
|
|
||||||
|
const paginated = isPaginatedRoute(pageProps);
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const canonicalURL = createCanonicalURL('.' + pathname, site ?? url.origin);
|
const canonicalURL = createCanonicalURL('.' + pathname, site ?? url.origin, paginated);
|
||||||
const response: ResponseInit = {
|
const response: ResponseInit = {
|
||||||
status: 200,
|
status: 200,
|
||||||
statusText: 'OK',
|
statusText: 'OK',
|
||||||
|
|
|
@ -3,9 +3,12 @@ import type { ModuleNode, ViteDevServer } from 'vite';
|
||||||
import type { Metadata } from '../../runtime/server/metadata.js';
|
import type { Metadata } from '../../runtime/server/metadata.js';
|
||||||
|
|
||||||
/** Normalize URL to its canonical form */
|
/** Normalize URL to its canonical form */
|
||||||
export function createCanonicalURL(url: string, base?: string): URL {
|
export function createCanonicalURL(url: string, base?: string, paginated?: boolean): URL {
|
||||||
let pathname = url.replace(/\/index.html$/, ''); // index.html is not canonical
|
let pathname = url.replace(/\/index.html$/, ''); // index.html is not canonical
|
||||||
pathname = pathname.replace(/\/1\/?$/, ''); // neither is a trailing /1/ (impl. detail of collections)
|
// Only trim the first page's /1 param if Astro's paginated() was used
|
||||||
|
if (paginated) {
|
||||||
|
pathname = pathname.replace(/\/1\/?$/, ''); // neither is a trailing /1/ (impl. detail of collections)
|
||||||
|
}
|
||||||
if (!npath.extname(pathname)) pathname = pathname.replace(/(\/+)?$/, '/'); // add trailing slash if there’s no extension
|
if (!npath.extname(pathname)) pathname = pathname.replace(/(\/+)?$/, '/'); // add trailing slash if there’s no extension
|
||||||
pathname = pathname.replace(/\/+/g, '/'); // remove duplicate slashes (URL() won’t)
|
pathname = pathname.replace(/\/+/g, '/'); // remove duplicate slashes (URL() won’t)
|
||||||
return new URL(pathname, base);
|
return new URL(pathname, base);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { loadFixture } from './test-utils.js';
|
import { loadFixture } from './test-utils.js';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
|
||||||
describe('getStaticPaths - build calls', () => {
|
describe('getStaticPaths - build calls', () => {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
@ -59,7 +60,7 @@ describe('getStaticPaths - route params type validation', () => {
|
||||||
devServer = await fixture.startDevServer();
|
devServer = await fixture.startDevServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves 200 on mathcing static path - string params', async () => {
|
it('resolves 200 on matching static path - string params', async () => {
|
||||||
// route provided with { params: { year: "2022", slug: "post-2" }}
|
// route provided with { params: { year: "2022", slug: "post-2" }}
|
||||||
const res = await fixture.fetch('/blog/2022/post-1');
|
const res = await fixture.fetch('/blog/2022/post-1');
|
||||||
expect(res.status).to.equal(200);
|
expect(res.status).to.equal(200);
|
||||||
|
@ -71,3 +72,34 @@ describe('getStaticPaths - route params type validation', () => {
|
||||||
expect(res.status).to.equal(200);
|
expect(res.status).to.equal(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe ('getStaticPaths - numeric route params', () => {
|
||||||
|
let fixture;
|
||||||
|
let devServer;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/astro-get-static-paths/',
|
||||||
|
site: 'https://mysite.dev/'
|
||||||
|
});
|
||||||
|
devServer = await fixture.startDevServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await devServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves 200 on matching static paths', async () => {
|
||||||
|
// routes params provided for pages /posts/1, /posts/2, and /posts/3
|
||||||
|
for (const page of [1, 2, 3]) {
|
||||||
|
let res = await fixture.fetch(`/posts/${page}`);
|
||||||
|
expect(res.status).to.equal(200);
|
||||||
|
|
||||||
|
const html = await res.text();
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const canonical = $('link[rel=canonical]');
|
||||||
|
expect(canonical.attr('href')).to.equal(`https://mysite.dev/posts/${page}/`, `doesn't trim the /${page}/ route param`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
29
packages/astro/test/fixtures/astro-get-static-paths/src/pages/posts/[page].astro
vendored
Normal file
29
packages/astro/test/fixtures/astro-get-static-paths/src/pages/posts/[page].astro
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
params: { page: 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: { page: 2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: { page: 3 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const { page } = Astro.params
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>Posts Page {page}</title>
|
||||||
|
<link rel="canonical" href={Astro.canonicalURL.href}>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to page {page}</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue