Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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/tall-boxes-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Use custom x-sveltekit-action header to route enhanced form submissions to +page.server.js over +server.js
3 changes: 2 additions & 1 deletion packages/kit/src/runtime/app/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ export function enhance(form, submit = () => {}) {
const response = await fetch(action, {
method: 'POST',
headers: {
accept: 'application/json'
accept: 'application/json',
'x-sveltekit-action': 'true'
},
body: data,
signal: controller.signal
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,16 @@ export async function render_endpoint(event, mod, state) {
* @param {import('types').RequestEvent} event
*/
export function is_endpoint_request(event) {
const { method } = event.request;
const { method, headers } = event.request;

if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
// These methods exist exclusively for endpoints
return true;
}

// use:enhance uses a custom header to disambiguate
if (headers.get('x-sveltekit-action') === 'true') return false;

// GET/POST requests may be for endpoints or pages. We prefer endpoints if this isn't a text/html request
const accept = event.request.headers.get('accept') ?? '*/*';
return negotiate(accept, ['*', 'text/html']) !== 'text/html';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const actions = {
default: () => {
return {
submitted: true
};
}
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
<script>
import { enhance } from '$app/forms';

/** @type {import('./$types').ActionData} */
export let form;

let result;

/** @param {string} method */
async function request(method) {
result = 'loading';
const response = await fetch('/routing/endpoint-next-to-page', {method});
const response = await fetch('/routing/content-negotiation', { method });
result = await response.text();
}
</script>
Expand All @@ -16,3 +21,9 @@
<button on:click={() => request('POST')}>POST</button>
<button on:click={() => request('DELETE')}>DELETE</button>
<pre>{result}</pre>

<form method="POST" use:enhance>
<button>Submit</button>
</form>

<p data-testid="form-result">form.submitted: {form?.submitted}</p>
32 changes: 21 additions & 11 deletions packages/kit/test/apps/basics/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -900,15 +900,25 @@ test.describe('data-sveltekit attributes', () => {
});
});

test('+server.js next to +page.svelte works', async ({ page }) => {
await page.goto('/routing/endpoint-next-to-page');
expect(await page.textContent('p')).toBe('Hi');

for (const method of ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) {
await page.click(`button:has-text("${method}")`);
await page.waitForFunction(
(method) => document.querySelector('pre').textContent === method,
method
);
}
test.describe('Content negotiation', () => {
test('+server.js next to +page.svelte works', async ({ page }) => {
await page.goto('/routing/content-negotiation');
expect(await page.textContent('p')).toBe('Hi');

for (const method of ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) {
await page.click(`button:has-text("${method}")`);
await page.waitForFunction(
(method) => document.querySelector('pre')?.textContent === method,
method
);
}
});

test('use:enhance uses action, not POST handler', async ({ page }) => {
await page.goto('/routing/content-negotiation');

page.click('button:has-text("Submit")');
await page.waitForResponse('/routing/content-negotiation');
await expect(page.locator('[data-testid="form-result"]')).toHaveText('form.submitted: true');
});
});