@gusnips/vite 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/head.d.ts.map +1 -1
- package/dist/head.js +19 -7
- package/dist/head.js.map +1 -1
- package/dist/sitemap.d.ts +4 -0
- package/dist/sitemap.d.ts.map +1 -1
- package/dist/sitemap.js +4 -0
- package/dist/sitemap.js.map +1 -1
- package/package.json +2 -2
- package/src/head.test.ts +22 -0
- package/src/head.ts +19 -7
- package/src/sitemap.ts +4 -0
package/README.md
CHANGED
|
@@ -41,12 +41,14 @@ const html = bakeHead(template, {
|
|
|
41
41
|
|
|
42
42
|
Four rules in there are load-bearing:
|
|
43
43
|
|
|
44
|
-
- **`canonical: null` strips the tag, and
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
- **`canonical: null` strips the tag, and `og:url` with it.** For the 404 shell, which is served
|
|
45
|
+
for every address that does not exist. Blanking is not stripping: an empty canonical is a claim
|
|
46
|
+
about `""`, and an empty `og:url` is a card pointing at your front page. **Leave `image` out
|
|
47
|
+
there too**, and the shell keeps the card your template already has. The image is the half
|
|
48
|
+
people miss, and `bakeHead` cannot catch it for you — it writes whatever card you name. Three
|
|
49
|
+
codebases named the shell's card after its made-up path, so their `404.html` advertised
|
|
50
|
+
`/og/__not-found__.png`, a file that has never existed. Every share of a dead link unfurled
|
|
51
|
+
broken, and nothing in a browser showed it.
|
|
50
52
|
- **`og:locale` is not optional on a non-English page.** Leave it out and the spec does not
|
|
51
53
|
default it to "unknown" — it defaults to `en_US`. A Portuguese page with a Portuguese
|
|
52
54
|
`og:title` then tells every share crawler the card is English. `ogLocale("pt-BR")` gives you
|
|
@@ -84,9 +86,14 @@ build instead of shipping.
|
|
|
84
86
|
That `pageFile` at the top writes `pricing.html`, not `pricing/index.html`. Nested routes keep
|
|
85
87
|
their folders — `/guides/errors` → `guides/errors.html`.
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
Every address your app puts in a canonical or a sitemap has to answer 200 rather than redirect. On
|
|
90
|
+
Cloudflare Pages that means flat files: it serves `pricing/index.html` at `/pricing/` and answers
|
|
91
|
+
`/pricing` with a 308. A flat file answers both.
|
|
92
|
+
|
|
93
|
+
Hosts differ, though. Firebase Hosting redirects `/pricing` to `/pricing/` by default, and serves
|
|
94
|
+
`pricing/index.html` at `/pricing` itself once `trailingSlash` is `false`. On a host like that,
|
|
95
|
+
write the directory form and set the flag. Either way, run `curl -I` on one page before you trust
|
|
96
|
+
the sitemap.
|
|
90
97
|
|
|
91
98
|
## Sitemap, robots, OG cards
|
|
92
99
|
|
package/dist/head.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"AAiBA,4FAA4F;AAC5F,eAAO,MAAM,UAAU,4BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"AAiBA,4FAA4F;AAC5F,eAAO,MAAM,UAAU,4BAA0B,CAAC;AAgClD,uFAAuF;AACvF,MAAM,WAAW,SAAS;IACxB;yBACqB;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;2DACuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;gDAC4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAmGjE;AAED,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;uDACmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,cAAmB,GAC1B,IAAI,CAqCN"}
|
package/dist/head.js
CHANGED
|
@@ -32,11 +32,12 @@ function setMeta(html, selector, value) {
|
|
|
32
32
|
/**
|
|
33
33
|
* The same, for a tag a template is allowed not to carry.
|
|
34
34
|
*
|
|
35
|
-
* Only the `twitter:`
|
|
36
|
-
* `twitter:` ones are absent,
|
|
37
|
-
* omits them is correct, and demanding them would break an app
|
|
38
|
-
* that DOES carry one and gets a stale value is still a bug, which
|
|
39
|
-
* rather than ignored. Everything a crawler actually depends on goes
|
|
35
|
+
* Only the `twitter:` tags, `og:image:alt` and `name="title"` get this. X reads the `og:` tags when
|
|
36
|
+
* the `twitter:` ones are absent, no crawler reads `name="title"` at all, and most templates carry
|
|
37
|
+
* no image alt — so a template that omits them is correct, and demanding them would break an app
|
|
38
|
+
* that never had them. A template that DOES carry one and gets a stale value is still a bug, which
|
|
39
|
+
* is why they are written rather than ignored. Everything a crawler actually depends on goes
|
|
40
|
+
* through `setMeta`.
|
|
40
41
|
*/
|
|
41
42
|
function setMetaIfPresent(html, selector, value) {
|
|
42
43
|
const re = metaRe(selector);
|
|
@@ -76,6 +77,10 @@ export function bakeHead(template, tags) {
|
|
|
76
77
|
if (tags.image !== undefined) {
|
|
77
78
|
html = setMeta(html, 'property="og:image"', tags.image);
|
|
78
79
|
html = setMetaIfPresent(html, 'property="twitter:image"', tags.image);
|
|
80
|
+
// A card's description goes with the card. Left alone, every page's own card was described
|
|
81
|
+
// with the front page's title — the stale value this function exists to prevent, on the one
|
|
82
|
+
// tag written for somebody who cannot see the picture.
|
|
83
|
+
html = setMetaIfPresent(html, 'property="og:image:alt"', shareTitle);
|
|
79
84
|
}
|
|
80
85
|
if (tags.canonical === null) {
|
|
81
86
|
// Strip rather than blank: an empty canonical is a claim about "" and an empty og:url is a
|
|
@@ -113,8 +118,15 @@ export function bakeHead(template, tags) {
|
|
|
113
118
|
.map((alt) => ` <meta property="og:locale:alternate" content="${escapeAttr(ogLocale(alt.hreflang))}" />`);
|
|
114
119
|
html = html.replace("</head>", ` <meta property="og:locale" content="${escapeAttr(ogLocale(tags.lang))}" />\n${alternates.length ? `${alternates.join("\n")}\n` : ""} </head>`);
|
|
115
120
|
}
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
// A template that says `index, follow` gets its tag rewritten, not a second one beside it. A head
|
|
122
|
+
// carrying both leaves a contradiction to each crawler's own tie-break rule, and one adopter's
|
|
123
|
+
// 404 shipped exactly that.
|
|
124
|
+
if (tags.noindex) {
|
|
125
|
+
const robots = metaRe('name="robots"');
|
|
126
|
+
html = robots.test(html)
|
|
127
|
+
? html.replace(robots, "$1noindex$2")
|
|
128
|
+
: html.replace("</head>", ` <meta name="robots" content="noindex" />\n </head>`);
|
|
129
|
+
}
|
|
118
130
|
if (tags.headExtra)
|
|
119
131
|
html = html.replace("</head>", ` ${tags.headExtra}\n </head>`);
|
|
120
132
|
if (tags.lang !== undefined) {
|
package/dist/head.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head.js","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,yFAAyF;AACzF,8FAA8F;AAC9F,yDAAyD;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,4FAA4F;AAC5F,MAAM,CAAC,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAElD,mGAAmG;AACnG,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAU,EAAE,CAC1C,IAAI,MAAM,CAAC,cAAc,WAAW,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC;AAE3E;;;;GAIG;AACH,SAAS,OAAO,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAa;IAC5D,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,QAAQ,2BAA2B,CAAC,CAAC;IAC7F,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"head.js","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"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"}
|
package/dist/sitemap.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ import type { Alternate } from "./head.ts";
|
|
|
7
7
|
* the app advertises in its canonical and its sitemap would be a redirect rather than a page.
|
|
8
8
|
* A flat file is served at `/pricing` AND `/pricing/`, 200 either way.
|
|
9
9
|
*
|
|
10
|
+
* That is Cloudflare Pages, not every host. Firebase Hosting serves the directory form at
|
|
11
|
+
* `/pricing` itself with `trailingSlash: false`, and an adopter there names its own files. What
|
|
12
|
+
* holds on every host is the rule underneath: an advertised address answers 200.
|
|
13
|
+
*
|
|
10
14
|
* A nested route keeps its folders: `/guides/errors` → `guides/errors.html`.
|
|
11
15
|
*/
|
|
12
16
|
export declare function pageFile(routePath: string): string;
|
package/dist/sitemap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../src/sitemap.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C
|
|
1
|
+
{"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../src/sitemap.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;CAC5F;AAED;+CAC+C;AAC/C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC;IACjB;iEAC6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;iFAC6E;IAC7E,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CACnC;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAyBnE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAYjG;AAED,MAAM,WAAW,aAAa;IAC5B;;uFAEmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf;;;;iDAI6C;IAC7C,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;0FACsF;IACtF,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAa,EAAE,EAAE,aAAa,GAAG,MAAM,CAQpF"}
|
package/dist/sitemap.js
CHANGED
|
@@ -13,6 +13,10 @@ import { escapeAttr } from "./escape.js";
|
|
|
13
13
|
* the app advertises in its canonical and its sitemap would be a redirect rather than a page.
|
|
14
14
|
* A flat file is served at `/pricing` AND `/pricing/`, 200 either way.
|
|
15
15
|
*
|
|
16
|
+
* That is Cloudflare Pages, not every host. Firebase Hosting serves the directory form at
|
|
17
|
+
* `/pricing` itself with `trailingSlash: false`, and an adopter there names its own files. What
|
|
18
|
+
* holds on every host is the rule underneath: an advertised address answers 200.
|
|
19
|
+
*
|
|
16
20
|
* A nested route keeps its folders: `/guides/errors` → `guides/errors.html`.
|
|
17
21
|
*/
|
|
18
22
|
export function pageFile(routePath) {
|
package/dist/sitemap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../src/sitemap.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC
|
|
1
|
+
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../src/sitemap.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gusnips/vite",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "The build-time half of a prerendered Vite SPA: head baking, sitemap, robots, the prerender driver, and a shared vite preset.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"release:major": "bun pm version major && bun publish --access public"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@gusnips/react": "0.4.
|
|
62
|
+
"@gusnips/react": "0.4.5"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"@tailwindcss/vite": "^4",
|
package/src/head.test.ts
CHANGED
|
@@ -104,6 +104,16 @@ describe("bakeHead", () => {
|
|
|
104
104
|
expect(html).toContain('<meta name="robots" content="noindex" />');
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
it("rewrites the template's own robots tag rather than contradicting it", () => {
|
|
108
|
+
const indexable = TEMPLATE.replace(
|
|
109
|
+
"</head>",
|
|
110
|
+
'<meta name="robots" content="index, follow" />\n</head>',
|
|
111
|
+
);
|
|
112
|
+
const html = bakeHead(indexable, { ...BASE, canonical: null, noindex: true });
|
|
113
|
+
expect(html.match(/name="robots"/g)).toHaveLength(1);
|
|
114
|
+
expect(html).toContain('<meta name="robots" content="noindex" />');
|
|
115
|
+
});
|
|
116
|
+
|
|
107
117
|
// An empty canonical is a claim about "" and an empty og:url is a share card pointing at the
|
|
108
118
|
// origin root, which is what the strip exists to avoid.
|
|
109
119
|
it("leaves no blank URL behind on a page with no canonical", () => {
|
|
@@ -119,6 +129,18 @@ describe("bakeHead", () => {
|
|
|
119
129
|
expect(html).toContain('<meta property="og:image" content="https://acme.com/og.png" />');
|
|
120
130
|
});
|
|
121
131
|
|
|
132
|
+
// Left alone, every page's card was described as the front page's card.
|
|
133
|
+
it("describes the page's own card, and leaves the brand card's description with it", () => {
|
|
134
|
+
const withAlt = TEMPLATE.replace(
|
|
135
|
+
"</head>",
|
|
136
|
+
' <meta property="og:image:alt" content="front" />\n </head>',
|
|
137
|
+
);
|
|
138
|
+
const page = bakeHead(withAlt, { ...BASE, image: "https://acme.com/og/pricing.png" });
|
|
139
|
+
expect(page).toContain('<meta property="og:image:alt" content="T" />');
|
|
140
|
+
const shell = bakeHead(withAlt, { ...BASE, canonical: null });
|
|
141
|
+
expect(shell).toContain('<meta property="og:image:alt" content="front" />');
|
|
142
|
+
});
|
|
143
|
+
|
|
122
144
|
it("lists every alternate, reciprocally, including this page's own", () => {
|
|
123
145
|
const html = bakeHead(TEMPLATE, {
|
|
124
146
|
...BASE,
|
package/src/head.ts
CHANGED
|
@@ -36,11 +36,12 @@ function setMeta(html: string, selector: string, value: string): string {
|
|
|
36
36
|
/**
|
|
37
37
|
* The same, for a tag a template is allowed not to carry.
|
|
38
38
|
*
|
|
39
|
-
* Only the `twitter:`
|
|
40
|
-
* `twitter:` ones are absent,
|
|
41
|
-
* omits them is correct, and demanding them would break an app
|
|
42
|
-
* that DOES carry one and gets a stale value is still a bug, which
|
|
43
|
-
* rather than ignored. Everything a crawler actually depends on goes
|
|
39
|
+
* Only the `twitter:` tags, `og:image:alt` and `name="title"` get this. X reads the `og:` tags when
|
|
40
|
+
* the `twitter:` ones are absent, no crawler reads `name="title"` at all, and most templates carry
|
|
41
|
+
* no image alt — so a template that omits them is correct, and demanding them would break an app
|
|
42
|
+
* that never had them. A template that DOES carry one and gets a stale value is still a bug, which
|
|
43
|
+
* is why they are written rather than ignored. Everything a crawler actually depends on goes
|
|
44
|
+
* through `setMeta`.
|
|
44
45
|
*/
|
|
45
46
|
function setMetaIfPresent(html: string, selector: string, value: string): string {
|
|
46
47
|
const re = metaRe(selector);
|
|
@@ -148,6 +149,10 @@ export function bakeHead(template: string, tags: HeadTags): string {
|
|
|
148
149
|
if (tags.image !== undefined) {
|
|
149
150
|
html = setMeta(html, 'property="og:image"', tags.image);
|
|
150
151
|
html = setMetaIfPresent(html, 'property="twitter:image"', tags.image);
|
|
152
|
+
// A card's description goes with the card. Left alone, every page's own card was described
|
|
153
|
+
// with the front page's title — the stale value this function exists to prevent, on the one
|
|
154
|
+
// tag written for somebody who cannot see the picture.
|
|
155
|
+
html = setMetaIfPresent(html, 'property="og:image:alt"', shareTitle);
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
if (tags.canonical === null) {
|
|
@@ -199,8 +204,15 @@ export function bakeHead(template: string, tags: HeadTags): string {
|
|
|
199
204
|
);
|
|
200
205
|
}
|
|
201
206
|
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
// A template that says `index, follow` gets its tag rewritten, not a second one beside it. A head
|
|
208
|
+
// carrying both leaves a contradiction to each crawler's own tie-break rule, and one adopter's
|
|
209
|
+
// 404 shipped exactly that.
|
|
210
|
+
if (tags.noindex) {
|
|
211
|
+
const robots = metaRe('name="robots"');
|
|
212
|
+
html = robots.test(html)
|
|
213
|
+
? html.replace(robots, "$1noindex$2")
|
|
214
|
+
: html.replace("</head>", ` <meta name="robots" content="noindex" />\n </head>`);
|
|
215
|
+
}
|
|
204
216
|
if (tags.headExtra) html = html.replace("</head>", ` ${tags.headExtra}\n </head>`);
|
|
205
217
|
|
|
206
218
|
if (tags.lang !== undefined) {
|
package/src/sitemap.ts
CHANGED
|
@@ -17,6 +17,10 @@ import type { Alternate } from "./head.ts";
|
|
|
17
17
|
* the app advertises in its canonical and its sitemap would be a redirect rather than a page.
|
|
18
18
|
* A flat file is served at `/pricing` AND `/pricing/`, 200 either way.
|
|
19
19
|
*
|
|
20
|
+
* That is Cloudflare Pages, not every host. Firebase Hosting serves the directory form at
|
|
21
|
+
* `/pricing` itself with `trailingSlash: false`, and an adopter there names its own files. What
|
|
22
|
+
* holds on every host is the rule underneath: an advertised address answers 200.
|
|
23
|
+
*
|
|
20
24
|
* A nested route keeps its folders: `/guides/errors` → `guides/errors.html`.
|
|
21
25
|
*/
|
|
22
26
|
export function pageFile(routePath: string): string {
|