フォームアクション
+page.server.js
ファイルは、<form>
要素を使用してサーバーにデータを POST
できる *actions* をエクスポートできます。
<form>
を使用する場合、クライアント側の JavaScript はオプションですが、JavaScript でフォームのインタラクションを簡単に *プログレッシブエンハンスメント* して、最高のユーザーエクスペリエンスを提供できます。
デフォルトアクション
最も簡単なケースでは、ページは default
アクションを宣言します。
/** @satisfies {import('./$types').Actions} */
export const const actions: {
default: (event: any) => Promise<void>;
}
actions = {
default: (event: any) => Promise<void>
default: async (event: any
event) => {
// TODO log the user in
}
};
import type { type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>
default: async (event: Kit.RequestEvent<Record<string, any>, string | null>
event) => {
// TODO log the user in
}
} satisfies type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;
/login
ページからこのアクションを呼び出すには、<form>
を追加するだけです。JavaScript は必要ありません。
<form method="POST">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
</form>
誰かがボタンをクリックすると、ブラウザはフォームデータを POST
リクエストでサーバーに送信し、デフォルトアクションを実行します。
GET
リクエストには副作用があってはならないため、アクションは常にPOST
リクエストを使用します。
action
属性を追加して、ページを指すことで、他のページからアクションを呼び出すこともできます(たとえば、ルートレイアウトのナビゲーションにログインウィジェットがある場合)。
<form method="POST" action="/login">
<!-- content -->
</form>
名前付きアクション
1つの default
アクションの代わりに、ページには必要なだけ名前付きアクションを含めることができます。
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: (event: any) => Promise<void>;
register: (event: any) => Promise<void>;
}
actions = {
default: async (event) => {
login: (event: any) => Promise<void>
login: async (event: any
event) => {
// TODO log the user in
},
register: (event: any) => Promise<void>
register: async (event: any
event) => {
// TODO register the user
}
};
import type { type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
default: async (event) => {
login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>
login: async (event: Kit.RequestEvent<Record<string, any>, string | null>
event) => {
// TODO log the user in
},
register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>
register: async (event: Kit.RequestEvent<Record<string, any>, string | null>
event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;
名前付きアクションを呼び出すには、名前の前に /
文字が付いたクエリパラメーターを追加します。
<form method="POST" action="?/register">
<form method="POST" action="/login?/register">
action
属性に加えて、ボタンで formaction
属性を使用して、親の <form>
とは異なるアクションに同じフォームデータを POST
することができます。
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
名前付きアクションにリダイレクトなしで POST した場合、クエリパラメーターが URL に残ってしまうため、名前付きアクションの横にデフォルトアクションを設定することはできません。つまり、次のデフォルト POST は以前の名前付きアクションを経由することになります。
アクションの構造
各アクションは RequestEvent
オブジェクトを受け取り、request.formData()
でデータを読み取ることができます。リクエストを処理した後(たとえば、Cookie を設定してユーザーをログインするなど)、アクションは、対応するページの form
プロパティと、次の更新までアプリ全体で $page.form
を通して利用できるデータで応答できます。
import * as module "$lib/server/db"
db from '$lib/server/db';
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load({ cookies: Cookies
Get or set cookies related to the current request
cookies }) {
const const user: any
user = await module "$lib/server/db"
db.getUserFromSession(cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('sessionid'));
return { user: any
user };
}
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>;
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>
login: async ({ cookies: Cookies
Get or set cookies related to the current request
cookies, request: Request
The original request object
request }) => {
const const data: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie
header to the response, but also make the cookie available via cookies.get
or cookies.getAll
during the current request.
The httpOnly
and secure
options are true
by default (except on http://localhost, where secure
is false
), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite
option defaults to lax
.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: boolean
success: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
register: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
// TODO register the user
}
};
import * as module "$lib/server/db"
db from '$lib/server/db';
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad, type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = async ({ cookies: Cookies
Get or set cookies related to the current request
cookies }) => {
const const user: any
user = await module "$lib/server/db"
db.getUserFromSession(cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('sessionid'));
return { user: any
user };
};
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>;
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>
login: async ({ cookies: Cookies
Get or set cookies related to the current request
cookies, request: Request
The original request object
request }) => {
const const data: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie
header to the response, but also make the cookie available via cookies.get
or cookies.getAll
during the current request.
The httpOnly
and secure
options are true
by default (except on http://localhost, where secure
is false
), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite
option defaults to lax
.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: boolean
success: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
register: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;
<script>
/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
let { data, form } = $props();
</script>
{#if form?.success}
<!-- this message is ephemeral; it exists because the page was rendered in
response to a form submission. it will vanish if the user reloads -->
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}
<script lang="ts">
import type { PageData, ActionData } from './$types';
let { data, form }: { data: PageData, form: ActionData } = $props();
</script>
{#if form?.success}
<!-- this message is ephemeral; it exists because the page was rendered in
response to a form submission. it will vanish if the user reloads -->
<p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}
レガシーモード
Svelte 4 では、プロパティを宣言するために、代わりに
export let data
とexport let form
を使用していました。
検証エラー
無効なデータが原因でリクエストを処理できなかった場合、以前に送信されたフォームの値とともに検証エラーをユーザーに返すことができるため、ユーザーは再試行できます。fail
関数を使用すると、(検証エラーの場合は通常 400 または 422 の)HTTP ステータスコードをデータとともに返すことができます。ステータスコードは $page.status
で、データは form
で利用できます。
import { function fail(status: number): ActionFailure<undefined> (+1 overload)
Create an ActionFailure
object.
fail } from '@sveltejs/kit';
import * as module "$lib/server/db"
db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
...;
}> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> | {
...;
}>
login: async ({ cookies: Cookies
Get or set cookies related to the current request
cookies, request: Request
The original request object
request }) => {
const const data: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
if (!const email: FormDataEntryValue | null
email) {
return fail<{
email: string | null;
missing: boolean;
}>(status: number, data: {
email: string | null;
missing: boolean;
}): ActionFailure<{
email: string | null;
missing: boolean;
}> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: string | null
email, missing: boolean
missing: true });
}
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue
email);
if (!const user: any
user || const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue
email, incorrect: boolean
incorrect: true });
}
cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie
header to the response, but also make the cookie available via cookies.get
or cookies.getAll
during the current request.
The httpOnly
and secure
options are true
by default (except on http://localhost, where secure
is false
), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite
option defaults to lax
.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: boolean
success: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
register: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
// TODO register the user
}
};
import { function fail(status: number): ActionFailure<undefined> (+1 overload)
Create an ActionFailure
object.
fail } from '@sveltejs/kit';
import * as module "$lib/server/db"
db from '$lib/server/db';
import type { type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
...;
}> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> | {
...;
}>
login: async ({ cookies: Cookies
Get or set cookies related to the current request
cookies, request: Request
The original request object
request }) => {
const const data: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
if (!const email: FormDataEntryValue | null
email) {
return fail<{
email: string | null;
missing: boolean;
}>(status: number, data: {
email: string | null;
missing: boolean;
}): ActionFailure<{
email: string | null;
missing: boolean;
}> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: string | null
email, missing: boolean
missing: true });
}
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue
email);
if (!const user: any
user || const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue
email, incorrect: boolean
incorrect: true });
}
cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie
header to the response, but also make the cookie available via cookies.get
or cookies.getAll
during the current request.
The httpOnly
and secure
options are true
by default (except on http://localhost, where secure
is false
), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite
option defaults to lax
.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: boolean
success: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
register: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;
念のため、メールアドレスのみをページに返送し、パスワードは返送しないことに注意してください。
<form method="POST" action="?/login">
{#if form?.missing}<p class="error">The email field is required</p>{/if}
{#if form?.incorrect}<p class="error">Invalid credentials!</p>{/if}
<label>
Email
<input name="email" type="email" value={form?.email ?? ''}>
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
返されるデータは、JSON としてシリアル化可能である必要があります。それ以外の場合、構造は完全にあなた次第です。たとえば、ページに複数のフォームがある場合、返された form
データがどの <form>
を参照しているかを、id
プロパティなどで区別できます。
リダイレクト
リダイレクト(およびエラー)は、load
とまったく同じように機能します。
import { function fail(status: number): ActionFailure<undefined> (+1 overload)
Create an ActionFailure
object.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect } from '@sveltejs/kit';
import * as module "$lib/server/db"
db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>
login: async ({ cookies: Cookies
Get or set cookies related to the current request
cookies, request: Request
The original request object
request, url: URL
The requested URL.
url }) => {
const const data: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
if (!const user: any
user) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, missing: boolean
missing: true });
}
if (const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, incorrect: boolean
incorrect: true });
}
cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie
header to the response, but also make the cookie available via cookies.get
or cookies.getAll
during the current request.
The httpOnly
and secure
options are true
by default (except on http://localhost, where secure
is false
), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite
option defaults to lax
.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
if (url: URL
The requested URL.
url.URL.searchParams: URLSearchParams
searchParams.URLSearchParams.has(name: string, value?: string): boolean
Returns a Boolean indicating if such a search parameter exists.
has('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect(303, url: URL
The requested URL.
url.URL.searchParams: URLSearchParams
searchParams.URLSearchParams.get(name: string): string | null
Returns the first value associated to the given search parameter.
get('redirectTo'));
}
return { success: boolean
success: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
register: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
// TODO register the user
}
};
import { function fail(status: number): ActionFailure<undefined> (+1 overload)
Create an ActionFailure
object.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect } from '@sveltejs/kit';
import * as module "$lib/server/db"
db from '$lib/server/db';
import type { type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>
login: async ({ cookies: Cookies
Get or set cookies related to the current request
cookies, request: Request
The original request object
request, url: URL
The requested URL.
url }) => {
const const data: FormData
data = await request: Request
The original request object
request.Body.formData(): Promise<FormData>
formData();
const const email: FormDataEntryValue | null
email = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('email');
const const password: FormDataEntryValue | null
password = const data: FormData
data.FormData.get(name: string): FormDataEntryValue | null
get('password');
const const user: any
user = await module "$lib/server/db"
db.getUser(const email: FormDataEntryValue | null
email);
if (!const user: any
user) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, missing: boolean
missing: true });
}
if (const user: any
user.password !== module "$lib/server/db"
db.hash(const password: FormDataEntryValue | null
password)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure
object.
fail(400, { email: FormDataEntryValue | null
email, incorrect: boolean
incorrect: true });
}
cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie
header to the response, but also make the cookie available via cookies.get
or cookies.getAll
during the current request.
The httpOnly
and secure
options are true
by default (except on http://localhost, where secure
is false
), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite
option defaults to lax
.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"
db.createSession(const user: any
user), { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
if (url: URL
The requested URL.
url.URL.searchParams: URLSearchParams
searchParams.URLSearchParams.has(name: string, value?: string): boolean
Returns a Boolean indicating if such a search parameter exists.
has('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): never
Redirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect(303, url.searchParams.get('redirectTo'));
}
return { success: boolean
success: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
register: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;
データの読み込み
アクションの実行後、ページは(リダイレクトまたは予期しないエラーが発生しない限り)再レンダリングされ、アクションの戻り値が form
プロップとしてページで利用できるようになります。これは、ページが読み込まれた後、ページ内の load
関数が実行されることを意味します。
handle
はアクションが呼び出される前に実行され、load
関数の前に再実行されないことに注意してください。たとえば、handle
を使用して Cookie に基づいて event.locals
を設定する場合、アクションで Cookie を設定または削除するときに event.locals
を更新する必要があります。
/** @type {import('@sveltejs/kit').Handle} */
export async function function handle(input: {
event: RequestEvent;
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}): MaybePromise<...>
handle({ event: RequestEvent<Partial<Record<string, string>>, string | null>
event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>
resolve }) {
event: RequestEvent<Partial<Record<string, string>>, string | null>
event.RequestEvent<Partial<Record<string, string>>, string | null>.locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user: {
name: string;
} | null
user = await function getUser(sessionid: string | undefined): {
name: string;
}
getUser(event: RequestEvent<Partial<Record<string, string>>, string | null>
event.RequestEvent<Partial<Record<string, string>>, string | null>.cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>
resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>
event);
}
import type { type Handle = (input: {
event: RequestEvent;
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}) => MaybePromise<...>
The handle
hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event
object representing the request and a function called resolve
, which renders the route and generates a Response
.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle } from '@sveltejs/kit';
export const const handle: Handle
handle: type Handle = (input: {
event: RequestEvent;
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}) => MaybePromise<...>
The handle
hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event
object representing the request and a function called resolve
, which renders the route and generates a Response
.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle = async ({ event: RequestEvent<Partial<Record<string, string>>, string | null>
event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>
resolve }) => {
event: RequestEvent<Partial<Record<string, string>>, string | null>
event.RequestEvent<Partial<Record<string, string>>, string | null>.locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user: {
name: string;
} | null
user = await function getUser(sessionid: string | undefined): {
name: string;
}
getUser(event: RequestEvent<Partial<Record<string, string>>, string | null>
event.RequestEvent<Partial<Record<string, string>>, string | null>.cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefined
Gets a cookie that was previously set with cookies.set
, or from the request headers.
get('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>
resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>
event);
};
/** @type {import('./$types').PageServerLoad} */
export function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>
event) {
return {
user: {
name: string;
} | null
user: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>
event.RequestEvent<Record<string, any>, string | null>.locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user: {
name: string;
} | null
user
};
}
/** @satisfies {import('./$types').Actions} */
export const const actions: {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
logout: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
event: RequestEvent<Record<string, any>, string | null>
event.RequestEvent<Record<string, any>, string | null>.cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.delete(name: string, opts: CookieSerializeOptions & {
path: string;
}): void
Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
delete('sessionid', { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
event: RequestEvent<Record<string, any>, string | null>
event.RequestEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, RouteId extends string | null = string | null>.locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user: {
name: string;
} | null
user = null;
}
};
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad, type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const load: PageServerLoad
load: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>
event) => {
return {
user: {
name: string;
} | null
user: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>
event.RequestEvent<Record<string, any>, string | null>.locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user: {
name: string;
} | null
user
};
};
export const const actions: {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>
logout: async (event: RequestEvent<Record<string, any>, string | null>
event) => {
event: RequestEvent<Record<string, any>, string | null>
event.RequestEvent<Record<string, any>, string | null>.cookies: Cookies
Get or set cookies related to the current request
cookies.Cookies.delete(name: string, opts: CookieSerializeOptions & {
path: string;
}): void
Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
You must specify a path
for the cookie. In most cases you should explicitly set path: '/'
to make the cookie available throughout your app. You can use relative paths, or set path: ''
to make the cookie only available on the current path and its children
delete('sessionid', { path: string
Specifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path
Set-Cookie
attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
event: RequestEvent<Record<string, any>, string | null>
event.RequestEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, RouteId extends string | null = string | null>.locals: App.Locals
Contains custom data that was added to the request within the server handle hook
.
locals.App.Locals.user: {
name: string;
} | null
user = null;
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;
プログレッシブエンハンスメント
前のセクションでは、クライアント側の JavaScript なしで動作する /login
アクションを構築しました。つまり、fetch
はどこにもありません。それは素晴らしいことですが、JavaScript が利用可能な場合、フォームのインタラクションをプログレッシブに拡張して、より良いユーザーエクスペリエンスを提供できます。
use:enhance
フォームをプログレッシブに拡張する最も簡単な方法は、use:enhance
アクションを追加することです。
<script>
import { enhance } from '$app/forms';
/** @type {{ form: import('./$types').ActionData }} */
let { form } = $props();
</script>
<form method="POST" use:enhance>
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props();
</script>
<form method="POST" use:enhance>
use:enhance
はmethod="POST"
を持つフォームでのみ使用できます。指定されたメソッドがないフォームのデフォルトであるmethod="GET"
では機能しません。method="POST"
を持たないフォームでuse:enhance
を使用しようとすると、エラーが発生します。
ええ、
enhance
アクションと<form action>
の両方が「action」と呼ばれているのは少し紛らわしいですね。これらのドキュメントはアクション満載です。すみません。
引数なしで、use:enhance
はブラウザネイティブの動作をエミュレートします。ただし、ページ全体の再読み込みは行いません。以下を行います。
- アクションが送信元のページと同じページにある場合にのみ、成功または無効な応答があったときに
form
プロパティ、$page.form
および$page.status
を更新します。たとえば、フォームが<form action="/somewhere/else" ..>
のようになっている場合、form
と$page
は更新されません。これは、ネイティブフォームの送信ケースでは、アクションがあるページにリダイレクトされるためです。どちらの場合も更新したい場合は、applyAction
を使用してください。 <form>
要素をリセットします- 成功した応答で
invalidateAll
を使用してすべてのデータを無効にします - リダイレクト応答で
goto
を呼び出します - エラーが発生した場合、最も近い
+error
バウンダリをレンダリングします - 適切な要素にフォーカスをリセットします
use:enhance のカスタマイズ
動作をカスタマイズするには、フォームが送信される直前に実行される SubmitFunction
を指定し、(オプションで) ActionResult
で実行されるコールバックを返すことができます。コールバックを返す場合、上記のデフォルトの動作はトリガーされないことに注意してください。元に戻すには、update
を呼び出します。
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel, submitter }) => {
// `formElement` is this `<form>` element
// `formData` is its `FormData` object that's about to be submitted
// `action` is the URL to which the form is posted
// calling `cancel()` will prevent the submission
// `submitter` is the `HTMLElement` that caused the form to be submitted
return async ({ result, update }) => {
// `result` is an `ActionResult` object
// `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
};
}}
>
これらの関数を使用して、読み込み UI などを表示および非表示にできます。
コールバックを返す場合は、デフォルトの use:enhance
動作の一部を再現する必要がある場合がありますが、成功した応答ですべてのデータを無効にする必要はありません。applyAction
でそれを行うことができます
<script>
import { enhance, applyAction } from '$app/forms';
/** @type {{ form: import('./$types').ActionData }} */
let { form } = $props();
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` is an `ActionResult` object
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
>
<script lang="ts">
import { enhance, applyAction } from '$app/forms';
import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props();
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` is an `ActionResult` object
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
>
applyAction(result)
の動作は result.type
によって異なります
success
、failure
—$page.status
をresult.status
に設定し、(enhance
のupdate
とは対照的に) 送信元に関係なく、form
と$page.form
をresult.data
に更新しますredirect
—goto(result.location, { invalidateAll: true })
を呼び出しますerror
— 最も近い+error
バウンダリをresult.error
でレンダリングします
いずれの場合も、フォーカスがリセットされます。
カスタムイベントリスナー
use:enhance
を使用せずに、<form>
の通常のイベントリスナーを使用して、プログレッシブエンハンスメントを自身で実装することもできます。
<script>
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
/** @type {{ form: import('./$types').ActionData }} */
let { form } = $props();
/** @param {{ currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
/** @type {import('@sveltejs/kit').ActionResult} */
const result = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" onsubmit|preventDefault={handleSubmit}>
<!-- content -->
</form>
<script lang="ts">
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
import type { ActionData } from './$types';
import type { ActionResult } from '@sveltejs/kit';
let { form }: { form: ActionData } = $props();
async function handleSubmit(event: { currentTarget: EventTarget & HTMLFormElement}) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
const result: ActionResult = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" onsubmit|preventDefault={handleSubmit}>
<!-- content -->
</form>
$app/forms
の対応する方法を使用して、さらに処理を進める前に応答を deserialize
する必要があることに注意してください。フォームアクションは load
関数と同様に、Date
または BigInt
オブジェクトを返すこともサポートしているため、JSON.parse()
だけでは十分ではありません。
+page.server.js
とともに +server.js
がある場合、fetch
リクエストはデフォルトでそこにルーティングされます。代わりに +page.server.js
のアクションに POST
するには、カスタムの x-sveltekit-action
ヘッダーを使用します。
const const response: Response
response = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch(this.action, {
RequestInit.method?: string | undefined
A string to set request’s method.
method: 'POST',
RequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request’s body.
body: data,
RequestInit.headers?: HeadersInit | undefined
A Headers object, an object literal, or an array of two-item arrays to set request’s headers.
headers: {
'x-sveltekit-action': 'true'
}
});
代替案
フォームアクションはサーバーにデータを送信するための推奨される方法であり、プログレッシブに拡張できますが、+server.js
ファイルを使用して (たとえば) JSON API を公開することもできます。このようなインタラクションは次のようになります。
<script>
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button onclick={rerun}>Rerun CI</button>
/** @type {import('./$types').RequestHandler} */
export function function POST(): void
POST() {
// do something
}
import type { type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler } from './$types';
export const const POST: RequestHandler
POST: type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler = () => {
// do something
};
GET vs POST
見てきたように、フォームアクションを呼び出すには、method="POST"
を使用する必要があります。
一部のフォームでは、サーバーにデータを POST
する必要はありません。たとえば、検索入力です。これらについては、method="GET"
(または、同等の method
なし) を使用できます。SvelteKit はそれらを <a>
要素のように扱い、ページ全体のナビゲーションではなくクライアント側のルーターを使用します。
<form action="/search">
<label>
Search
<input name="q">
</label>
</form>
このフォームを送信すると、/search?q=...
に移動し、ロード関数が呼び出されますが、アクションは呼び出されません。<a>
要素と同様に、ルーターの動作を制御するために、<form>
に data-sveltekit-reload
、data-sveltekit-replacestate
、data-sveltekit-keepfocus
および data-sveltekit-noscroll
属性を設定できます。