Skip to content

Commit 5155028

Browse files
committed
fix: getServerSideProps fails when a file request match a router path pattern
1 parent ae1daec commit 5155028

File tree

5 files changed

+114
-84
lines changed

5 files changed

+114
-84
lines changed

examples/basic/src/components/Header.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ export function Header() {
3636
Products 2
3737
</a>
3838
</li>
39+
40+
<li>
41+
<a
42+
href="/products/3/agustin"
43+
className="text-white hover:text-indigo-200 transition-colors font-medium"
44+
>
45+
Product/:id/:name
46+
</a>
47+
</li>
48+
3949
<li>
4050
<a
4151
href="/not-found"

packages/pranx/src/client/ServerSidePage.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ export const ServerSidePage = (props: PropsWithChildren & { route_data: HydrateD
1414
const abortController = new AbortController();
1515

1616
try {
17-
const props_result = await ofetch<{ props: Record<string, any> }>(props.route_data.path, {
18-
method: "GET",
19-
query: {
20-
props: true,
21-
},
22-
signal: abortController.signal,
23-
});
17+
const props_result = await ofetch<{ props: Record<string, any> }>(
18+
props.route_data.server_data_api_url,
19+
{
20+
method: "GET",
21+
signal: abortController.signal,
22+
}
23+
);
2424

2525
set("props", props_result.props);
2626
} catch (error) {

packages/pranx/src/cmd/build.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { logger } from "@/utils/logger.js";
33
import { measureTime } from "@/utils/time-perf.js";
44
import fse from "fs-extra";
55
import kleur from "kleur";
6+
import crypto from "node:crypto";
67
import { join } from "pathe";
78
import { Fragment, h } from "preact";
89
import { renderToStringAsync } from "preact-render-to-string";
@@ -147,6 +148,7 @@ path: ${final_path}`);
147148
css: [css_output.entry, css_output[final_path] || ""].filter(Boolean),
148149
static_generated_routes: [],
149150
absolute_module_path: module_path,
151+
server_data_api_key: "",
150152
});
151153

152154
for (const static_path of static_paths_result.paths) {
@@ -197,6 +199,7 @@ params returned by getStaticPaths: ${JSON.stringify(static_path.params)}`);
197199
module: pages_relative_path,
198200
props: statics_fn_result.props,
199201
rendering_kind: isStatic ? "static" : "server-side",
202+
server_data_api_key: crypto.randomUUID(),
200203
revalidate: statics_fn_result.revalidate || -1,
201204
static_generated_routes: [],
202205
is_dynamic: isUrlDynamic,
@@ -216,6 +219,7 @@ params returned by getStaticPaths: ${JSON.stringify(static_path.params)}`);
216219
css: r.css,
217220
is_dynamic: r.is_dynamic,
218221
path_parsed_for_routing: filePathToRoutingPath(r.path),
222+
server_data_api_url: `/_internal_/${r.server_data_api_key}`,
219223
static_generated_routes: r.static_generated_routes.map((r) => {
220224
return {
221225
path: r.path,

packages/pranx/src/cmd/start.ts

Lines changed: 91 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ import { generate_html_template } from "@/build/generate_html_template.js";
1010
import { logger } from "@/utils/logger.js";
1111
import { measureTime } from "@/utils/time-perf.js";
1212
import fse from "fs-extra";
13-
import { defineHandler, H3, html, serve, serveStatic } from "h3";
13+
import {
14+
defineHandler,
15+
type EventHandlerRequest,
16+
H3,
17+
type H3Event,
18+
html,
19+
serve,
20+
serveStatic,
21+
} from "h3";
1422
import kleur from "kleur";
1523
import { readFile, stat } from "node:fs/promises";
16-
import { join, resolve } from "pathe";
24+
import { extname, join, resolve } from "pathe";
1725
import { Fragment, h } from "preact";
1826
import { renderToStringAsync } from "preact-render-to-string";
1927
import type { HYDRATE_DATA, PageModule, SERVER_MANIFEST, ServerEntryModule } from "types/index.js";
20-
import * as ufo from "ufo";
2128

2229
export async function start() {
2330
measureTime("pranx-start");
@@ -39,93 +46,69 @@ export async function start() {
3946
const { default: page, getServerSideProps } = (await import(file_absolute)) as PageModule;
4047

4148
const hydrate_data = (await fse.readJSON(SITE_MANIFEST_OUTPUT_PATH)) as HYDRATE_DATA;
49+
const url_for_routing_match = filePathToRoutingPath(route.path, false);
4250

4351
app.on(
4452
"GET",
45-
filePathToRoutingPath(route.path, false),
46-
defineHandler({
47-
middleware: [],
48-
meta: {},
49-
handler: async (event) => {
50-
const url_parsed = ufo.parseURL(event.req.url);
51-
const params = new URLSearchParams(url_parsed.search);
52-
const return_only_props = Boolean(params.get("props"));
53-
54-
let props_to_return = {};
55-
56-
if (getServerSideProps) {
57-
props_to_return = await getServerSideProps();
58-
}
59-
60-
if (return_only_props) {
61-
return {
62-
props: props_to_return,
63-
};
64-
}
65-
66-
const target_route_index = hydrate_data.routes.findIndex((r) => r.path === route.path);
67-
68-
if (target_route_index === -1 || !hydrate_data.routes[target_route_index]) {
69-
logger.error(`Route not found in hydrate data: ${route.path}`);
70-
event.res.status = 500;
71-
return html(event, "Internal Server Error");
72-
}
73-
74-
hydrate_data.routes[target_route_index].props = props_to_return;
75-
76-
const page_prerendered = await renderToStringAsync(
77-
h(server_entry_module?.default || Fragment, {}, h(page, props_to_return, null))
78-
);
79-
80-
const html_string = generate_html_template({
81-
page_prerendered,
82-
hydrate_data_as_string: JSON.stringify(hydrate_data),
83-
minify: true,
84-
css: route.css,
85-
});
86-
87-
event.res.headers.set("Content-Type", "text/html");
88-
89-
return html(event, html_string);
90-
},
91-
})
92-
);
93-
}
94-
}
53+
url_for_routing_match,
54+
defineHandler(async (event) => {
55+
// For files that match with dynamic routes
56+
if (extname(event.url.pathname.split("/").at(-1) || "")) {
57+
return createServeStatic(event);
58+
}
59+
60+
let props_to_return = {};
61+
62+
if (getServerSideProps) {
63+
props_to_return = await getServerSideProps();
64+
}
9565

96-
app.on("GET", "**", (event) => {
97-
return serveStatic(event, {
98-
indexNames: ["/index.html"],
66+
const target_route_index = hydrate_data.routes.findIndex((r) => r.path === route.path);
9967

100-
getContents: async (id) => {
101-
const target_file = join(OUTPUT_BUNDLE_BROWSER_DIR, id);
102-
const target_public_file = join(PUBLIC_USER_DIR, id);
68+
if (target_route_index === -1 || !hydrate_data.routes[target_route_index]) {
69+
logger.error(`Route not found in hydrate data: ${route.path}`);
70+
event.res.status = 500;
71+
return html(event, "Internal Server Error");
72+
}
10373

104-
const existsTargetFile = await fse.exists(target_file);
74+
hydrate_data.routes[target_route_index].props = props_to_return;
10575

106-
const buffer = await readFile(existsTargetFile ? target_file : target_public_file);
76+
const page_prerendered = await renderToStringAsync(
77+
h(server_entry_module?.default || Fragment, {}, h(page, props_to_return, null))
78+
);
10779

108-
return new Uint8Array(buffer);
109-
},
80+
const html_string = generate_html_template({
81+
page_prerendered,
82+
hydrate_data_as_string: JSON.stringify(hydrate_data),
83+
minify: true,
84+
css: route.css,
85+
});
11086

111-
getMeta: async (id) => {
112-
const target_file = join(OUTPUT_BUNDLE_BROWSER_DIR, id);
113-
const target_public_file = join(PUBLIC_USER_DIR, id);
87+
event.res.headers.set("Content-Type", "text/html");
11488

115-
const existsTargetFile = await fse.exists(target_file);
89+
return html(event, html_string);
90+
})
91+
);
11692

117-
const stats = await stat(existsTargetFile ? target_file : target_public_file).catch(
118-
() => {}
119-
);
93+
app.on(
94+
"GET",
95+
`/_internal_/${route.server_data_api_key}`,
96+
defineHandler(async () => {
97+
let props_to_return = {};
12098

121-
if (stats?.isFile()) {
122-
return stats;
123-
}
99+
if (getServerSideProps) {
100+
props_to_return = await getServerSideProps();
101+
}
124102

125-
return undefined;
126-
},
127-
});
128-
});
103+
return {
104+
props: props_to_return,
105+
};
106+
})
107+
);
108+
}
109+
}
110+
111+
app.on("GET", "**", (event) => createServeStatic(event));
129112

130113
const SERVER = serve(app, { port: PORT });
131114

@@ -135,3 +118,34 @@ export async function start() {
135118

136119
logger.success(`Start in ${START_TIME} ms`);
137120
}
121+
122+
const createServeStatic = (event: H3Event<EventHandlerRequest>) =>
123+
serveStatic(event, {
124+
indexNames: ["/index.html"],
125+
126+
getContents: async (id) => {
127+
const target_file = join(OUTPUT_BUNDLE_BROWSER_DIR, id);
128+
const target_public_file = join(PUBLIC_USER_DIR, id);
129+
130+
const existsTargetFile = await fse.exists(target_file);
131+
132+
const buffer = await readFile(existsTargetFile ? target_file : target_public_file);
133+
134+
return new Uint8Array(buffer);
135+
},
136+
137+
getMeta: async (id) => {
138+
const target_file = join(OUTPUT_BUNDLE_BROWSER_DIR, id);
139+
const target_public_file = join(PUBLIC_USER_DIR, id);
140+
141+
const existsTargetFile = await fse.exists(target_file);
142+
143+
const stats = await stat(existsTargetFile ? target_file : target_public_file).catch(() => {});
144+
145+
if (stats?.isFile()) {
146+
return stats;
147+
}
148+
149+
return undefined;
150+
},
151+
});

packages/pranx/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export type ServerManifestRoute = {
7777
is_dynamic: boolean;
7878
dynamic_params: Array<string>;
7979
css: string[];
80+
server_data_api_key: string;
8081
};
8182

8283
export type SERVER_MANIFEST = {
@@ -93,6 +94,7 @@ export type HydrateDataRoute = {
9394
css: string[];
9495
path_parsed_for_routing: string;
9596
static_generated_routes: Array<{ path: string; props: Record<string, any> }>;
97+
server_data_api_url: string;
9698
};
9799

98100
export type HYDRATE_DATA = {

0 commit comments

Comments
 (0)