Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dev-page-request-head-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solidjs/vite-plugin': patch
---

Under `vite dev`, pages now answer HEAD requests, and GET requests with `Accept: */*` or no Accept, the way production does. Before, these got Vite's 404. The page-request test now matches Vite's own HTML fallback.
39 changes: 36 additions & 3 deletions examples/start-ssr/test/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,33 @@ async function runHttpChecks(mode, origin) {
'post-flush redirect emits the script fallback',
post.html.includes('window.location') && post.html.includes('/redirected-target'),
);

// Pages answer what Vite's HTML fallback counts as navigations, as in
// production: HEAD, a `*/*` GET (curl, fetch()), a GET with no Accept.
const head = await rawRequest(origin + '/', { method: 'HEAD' });
record(
mode,
'http',
'HEAD answers like GET',
head.status === 200 && head.text === '',
`status ${head.status}`,
);
const anyType = await rawRequest(origin + '/', { headers: { accept: '*/*' } });
record(
mode,
'http',
'GET accepting */* renders the page',
anyType.status === 200 && anyType.text.includes('SSR Start Mode'),
`status ${anyType.status}`,
);
const noAccept = await rawRequest(origin + '/');
record(
mode,
'http',
'GET without Accept renders the page',
noAccept.status === 200 && noAccept.text.includes('SSR Start Mode'),
`status ${noAccept.status}`,
);
}

// Lazy asset-key checks (the /lazy-assets surface, src/App.tsx): a
Expand Down Expand Up @@ -1969,13 +1996,19 @@ async function runEndpointMode() {
functionId ? `got ${JSON.stringify(customText)}` : 'could not extract function id',
);

const fallback = await fetch(`${origin}/_server/${encodeURIComponent(functionId || '')}`);
const fallback = functionId
? await fetch(
`${origin}/_server/${encodeURIComponent(functionId)}?args=${encodeURIComponent('["endpoint"]')}`,
{ method: 'POST' },
)
: null;
const fallbackText = fallback ? await fallback.text() : '';
record(
mode,
'rpc',
'default endpoint no longer handled',
fallback.status !== 200,
`status ${fallback.status}`,
!!fallback && fallbackText !== 'hello endpoint from the server',
fallback ? `status ${fallback.status}` : 'could not extract function id',
);
} catch (e) {
record(
Expand Down
10 changes: 8 additions & 2 deletions src/ssr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1949,8 +1949,14 @@ export function startServe(
server.middlewares.use((req, res, next) => {
const url = new URL(req.url || '/', 'http://localhost');
if (url.pathname.startsWith('/@')) return next();
const accept = req.headers.accept || '';
const pageRequest = req.method === 'GET' && accept.includes('text/html');
// Vite's own HTML-fallback test: a GET or HEAD accepting HTML by
// name, via `*/*`, or with no Accept (curl, monitors). Production
// renders all of these; anything narrower 404s in dev only.
const accept = req.headers.accept;
const pageRequest =
(req.method === 'GET' || req.method === 'HEAD') &&
url.pathname !== '/favicon.ico' &&
(!accept || accept.includes('text/html') || accept.includes('*/*'));
// Production dispatches every request through the handler, so
// dev must too or API routes and no-JS form POSTs served by
// `start.middleware` are unreachable under `vite dev`. Without
Expand Down