Server Actions and Mutations

Sunucu Eylemleri, sunucuda yürütülen zaman uyumsuz işlevlerdir. Next.js uygulamalarında form gönderimleri ve veri mutasyonlarını işlemek için Sunucu ve İstemci Bileşenlerinde kullanılabilirler.

🎥 İzleyin: Sunucu Eylemleri ile formlar ve mutasyonlar hakkında daha fazla bilgi edinin → YouTube (10 dakika) .

Convention

Bir Sunucu Eylemi React ile tanımlanabilir "use server" yönergesini kullanabilirsiniz. Yönergeyi async işlevinin en üstüne yerleştirerek işlevi bir Sunucu Eylemi olarak işaretleyebilir ya da ayrı bir dosyanın en üstüne yerleştirerek bu dosyanın tüm dışa aktarımlarını Sunucu Eylemleri olarak işaretleyebilirsiniz.

Server Components

Sunucu Bileşenleri satır içi işlev düzeyini veya modül düzeyinde "use server" yönergesini kullanabilir. Bir Sunucu Eylemini satır içi yapmak için, "use server" adresini işlev gövdesinin en üstüne ekleyin:

app/page.tsx
// Server Component
export default function Page() {
  // Server Action
  async function create() {
    'use server'
 
    // ...
  }
 
  return (
    // ...
  )
}

Client Components

İstemci Bileşenleri yalnızca modül düzeyinde "use server" yönergesini kullanan eylemleri içe aktarabilir.

Bir İstemci Bileşeninde bir Sunucu Eylemi çağırmak için yeni bir dosya oluşturun ve dosyanın başına "use server" yönergesini ekleyin. Dosya içindeki tüm fonksiyonlar, hem İstemci hem de Sunucu Bileşenlerinde yeniden kullanılabilen Sunucu Eylemleri olarak işaretlenecektir:

app/actions.ts
'use server'
 
export async function create() {
  // ...
}
app/ui/button.tsx
import { create } from '@/app/actions'
 
export function Button() {
  return (
    // ...
  )
}

Bir Sunucu Eylemini bir İstemci Bileşenine prop olarak da aktarabilirsiniz:

<ClientComponent updateItem={updateItem} />
app/client-component.jsx
'use client'
 
export default function ClientComponent({ updateItem }) {
  return <form action={updateItem}>{/* ... */}</form>
}

Behavior

Examples

Forms

React, HTML'i genişletir <form> Sunucu Eylemlerinin action prop ile çağrılmasına izin vermek için öğe.

Bir formda çağrıldığında, eylem otomatik olarak FormData nesnesini kullanabilirsiniz. Alanları yönetmek için React useState kullanmanıza gerek yoktur, bunun yerine yerel FormData yöntemlerini kullanarak verileri ayıklayabilirsiniz :

app/invoices/page.tsx
export default function Page() {
  async function createInvoice(formData: FormData) {
    'use server'
 
    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }
 
    // mutate data
    // revalidate cache
  }
 
  return <form action={createInvoice}>...</form>
}

Bilmekte fayda var:

Passing Additional Arguments

JavaScript bind yöntemini kullanarak bir Sunucu Eylemine ek bağımsız değişkenler iletebilirsiniz.

app/client-component.tsx
'use client'
 
import { updateUser } from './actions'
 
export function UserProfile({ userId }: { userId: string }) {
  const updateUserWithId = updateUser.bind(null, userId)
 
  return (
    <form action={updateUserWithId}>
      <input type="text" name="name" />
      <button type="submit">Update User Name</button>
    </form>
  )
}

Sunucu Eylemi, form verilerine ek olarak userId bağımsız değişkenini de alır:

app/actions.js
'use server'
 
export async function updateUser(userId, formData) {
  // ...
}

Bildiğim iyi oldu:

  • Bir alternatif de argümanları formda gizli girdi alanları olarak iletmektir (örn. <input type="hidden" name="userId" value={userId} />). Ancak, değer işlenen HTML'nin bir parçası olacak ve kodlanmayacaktır.
  • .bind hem Sunucu hem de İstemci Bileşenlerinde çalışır. Ayrıca aşamalı geliştirmeyi de destekler.

Pending states

React'i kullanabilirsiniz useFormStatus Form gönderilirken beklemede durumunu göstermek için kanca.

app/submit-button.tsx
'use client'
 
import { useFormStatus } from 'react-dom'
 
export function SubmitButton() {
  const { pending } = useFormStatus()
 
  return (
    <button type="submit" aria-disabled={pending}>
      Add
    </button>
  )
}

<SubmitButton /> daha sonra herhangi bir biçimde iç içe geçebilir:

app/page.tsx
import { SubmitButton } from '@/app/submit-button'
import { createItem } from '@/app/actions'
 
// Server Component
export default async function Home() {
  return (
    <form action={createItem}>
      <input type="text" name="field-name" />
      <SubmitButton />
    </form>
  )
}

Server-side validation and error handling

Temel istemci tarafı form doğrulaması için required ve type="email" gibi HTML doğrulamalarını kullanmanızı öneririz.

Daha gelişmiş sunucu tarafı doğrulama için, verileri değiştirmeden önce form alanlarını doğrulamak üzere zod gibi bir kütüphane kullanabilirsiniz:

app/actions.ts
'use server'
 
import { z } from 'zod'
 
const schema = z.object({
  email: z.string({
    invalid_type_error: 'Invalid Email',
  }),
})
 
export default async function createUser(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
  })
 
  // Return early if the form data is invalid
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }
 
  // Mutate data
}

Alanlar sunucuda doğrulandıktan sonra, eyleminizde serileştirilebilir bir nesne döndürebilir ve React useFormState kullanıcıya bir mesaj göstermek için kanca.

app/actions.ts
'use server'
 
export async function createUser(prevState: any, formData: FormData) {
  // ...
  return {
    message: 'Please enter a valid email',
  }
}

Ardından, eyleminizi useFormState kancasına aktarabilir ve bir hata mesajı görüntülemek için döndürülen state adresini kullanabilirsiniz.

app/ui/signup.tsx
'use client'
 
import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'
 
const initialState = {
  message: '',
}
 
export function Signup() {
  const [state, formAction] = useFormState(createUser, initialState)
 
  return (
    <form action={formAction}>
      <label htmlFor="email">Email</label>
      <input type="text" id="email" name="email" required />
      {/* ... */}
      <p aria-live="polite" className="sr-only">
        {state?.message}
      </p>
      <button>Sign up</button>
    </form>
  )
}

Bilmekte fayda var:

  • Verileri değiştirmeden önce, her zaman bir kullanıcının eylemi gerçekleştirme yetkisine sahip olduğundan emin olmalısınız. Kimlik Doğrulama ve Yetkilendirme bölümüne bakın.

Optimistic updates

React'i kullanabilirsiniz useOptimistic kancası, yanıtı beklemek yerine Sunucu Eylemi bitmeden önce kullanıcı arayüzünü iyimser bir şekilde güncellemek için kullanılır:

app/page.tsx
'use client'
 
import { useOptimistic } from 'react'
import { send } from './actions'
 
type Message = {
  message: string
}
 
export function Thread({ messages }: { messages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(
    messages,
    (state: Message[], newMessage: string) => [
      ...state,
      { message: newMessage },
    ]
  )
 
  return (
    <div>
      {optimisticMessages.map((m, k) => (
        <div key={k}>{m.message}</div>
      ))}
      <form
        action={async (formData: FormData) => {
          const message = formData.get('message')
          addOptimisticMessage(message)
          await send(message)
        }}
      >
        <input type="text" name="message" />
        <button type="submit">Send</button>
      </form>
    </div>
  )
}

Nested elements

<button>, <input type="submit"> ve <input type="image"> gibi <form> içinde yuvalanmış öğelerde bir Sunucu Eylemi çağırabilirsiniz. Bu öğeler formAction prop veya olay işleyicilerini kabul eder.

Bu, bir form içinde birden fazla sunucu eylemi çağırmak istediğiniz durumlarda kullanışlıdır. Örneğin, bir gönderi taslağını yayınlamanın yanı sıra kaydetmek için belirli bir <button> öğesi oluşturabilirsiniz. Daha fazla bilgi için React <form> dokümanlarına bakın.

Programmatic form submission

Form gönderimini tetiklemek için requestSubmit() yöntemini kullanabilirsiniz. Örneğin, kullanıcı + Enter tuşlarına bastığında onKeyDown olayını dinleyebilirsiniz:

app/entry.tsx
'use client'
 
export function Entry() {
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (
      (e.ctrlKey || e.metaKey) &&
      (e.key === 'Enter' || e.key === 'NumpadEnter')
    ) {
      e.preventDefault()
      e.currentTarget.form?.requestSubmit()
    }
  }
 
  return (
    <div>
      <textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
    </div>
  )
}

Bu, Sunucu Eylemini çağıracak olan en yakın <form> atasının gönderimini tetikleyecektir.

Non-form Elements

Sunucu Eylemlerini <form> öğeleri içinde kullanmak yaygın olsa da, olay işleyicileri ve useEffect gibi kodunuzun diğer bölümlerinden de çağrılabilirler.

Event Handlers

Bir Sunucu Eylemini onClick gibi olay işleyicilerinden çağırabilirsiniz. Örneğin, bir beğeni sayısını artırmak için:

app/like-button.tsx
'use client'
 
import { incrementLike } from './actions'
import { useState } from 'react'
 
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)
 
  return (
    <>
      <p>Total Likes: {likes}</p>
      <button
        onClick={async () => {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      >
        Like
      </button>
    </>
  )
}

Kullanıcı deneyimini iyileştirmek için aşağıdaki gibi diğer React API'lerini kullanmanızı öneririz useOptimistic ve useTransition Sunucu Eylemi sunucuda yürütülmeyi bitirmeden önce kullanıcı arayüzünü güncellemek veya bekleyen bir durumu göstermek için.

Form öğelerine olay işleyicileri de ekleyebilirsiniz, örneğin bir form alanını kaydetmek için onChange:

app/ui/edit-post.tsx
'use client'
 
import { publishPost, saveDraft } from './actions'
 
export default function EditPost() {
  return (
    <form action={publishPost}>
      <textarea
        name="content"
        onChange={async (e) => {
          await saveDraft(e.target.value)
        }}
      />
      <button type="submit">Publish</button>
    </form>
  )
}

Birden fazla olayın hızlı bir şekilde art arda tetiklenebileceği bu gibi durumlarda, gereksiz Sunucu Eylemi çağrılarını önlemek için debouncing yapılmasını öneririz.

useEffect

React'i kullanabilirsiniz useEffect Bileşen bağlandığında veya bir bağımlılık değiştiğinde bir Sunucu Eylemini çağırmak için kanca. Bu, global olaylara bağlı olan veya otomatik olarak tetiklenmesi gereken mutasyonlar için kullanışlıdır. Örneğin, uygulama kısayolları için onKeyDown, sonsuz kaydırma için bir kesişim gözlemci kancası veya bileşen bir görünüm sayısını güncellemek için bağlandığında:

app/view-count.tsx
'use client'
 
import { incrementViews } from './actions'
import { useState, useEffect } from 'react'
 
export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)
 
  useEffect(() => {
    const updateViews = async () => {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    }
 
    updateViews()
  }, [])
 
  return <p>Total Views: {views}</p>
}

Davranışları ve uyarıları dikkate almayı unutmayın of useEffect.

Error Handling

Bir hata atıldığında, en yakın hata yakalanmış olacaktır error.js veya istemcide <Suspense> sınırı. Kullanıcı arayüzünüz tarafından işlenecek hataları döndürmek için try/catch adresini kullanmanızı öneririz.

Örneğin, Sunucu Eyleminiz yeni bir öğe oluştururken oluşan hataları bir mesaj döndürerek işleyebilir:

app/actions.ts
'use server'
 
export async function createTodo(prevState: any, formData: FormData) {
  try {
    // Mutate data
  } catch (e) {
    throw new Error('Failed to create task')
  }
}

Bilmekte fayda var:

Revalidating data

Next.js Önbelleğini Sunucu Eylemlerinizin içinde şu şekilde yeniden doğrulayabilirsiniz revalidatePath API:

app/actions.ts
'use server'
 
import { revalidatePath } from 'next/cache'
 
export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }
 
  revalidatePath('/posts')
}

Veya bir önbellek etiketi ile belirli bir veri getirmeyi geçersiz kılmak için revalidateTag:

app/actions.ts
'use server'
 
import { revalidateTag } from 'next/cache'
 
export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }
 
  revalidateTag('posts')
}

Redirecting

Bir Sunucu Eylemi tamamlandıktan sonra kullanıcıyı farklı bir rotaya yönlendirmek isterseniz redirect API. redirect adresinin try/catch bloğunun dışında çağrılması gerekir:

app/actions.ts
'use server'
 
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
 
export async function createPost(id: string) {
  try {
    // ...
  } catch (error) {
    // ...
  }
 
  revalidateTag('posts') // Update cached posts
  redirect(`/post/${id}`) // Navigate to the new post page
}

Cookies

get, set ve delete çerezlerini bir Sunucu Eylemi içinde cookies API:

app/actions.ts
'use server'
 
import { cookies } from 'next/headers'
 
export async function exampleAction() {
  // Get cookie
  const value = cookies().get('name')?.value
 
  // Set cookie
  cookies().set('name', 'Delba')
 
  // Delete cookie
  cookies().delete('name')
}

Sunucu Eylemleri'nden çerezleri silmek için ek örneklere bakın.

Security

Authentication and authorization

Sunucu Eylemlerine genel kullanıma yönelik API uç noktalarına davrandığınız gibi davranmalı ve kullanıcının eylemi gerçekleştirme yetkisine sahip olduğundan emin olmalısınız. Örneğin:

app/actions.ts
'use server'
 
import { auth } from './lib'
 
export function addItem() {
  const { user } = auth()
  if (!user) {
    throw new Error('You must be signed in to perform this action')
  }
 
  // ...
}

Closures and encryption

Bir bileşenin içinde bir Sunucu Eylemi tanımlamak, eylemin dış işlevin kapsamına erişebildiği bir kapanışı oluşturur. Örneğin, publish eyleminin publishVersion değişkenine erişimi vardır:

app/page.tsx
export default function Page() {
  const publishVersion = await getLatestVersion();
 
  async function publish(formData: FormData) {
    "use server";
    if (publishVersion !== await getLatestVersion()) {
      throw new Error('The version has changed since pressing publish');
    }
    ...
  }
 
  return <button action={publish}>Publish</button>;
}

Kapanışlar, daha sonra eylem çağrıldığında kullanılabilmesi için işleme sırasında bir veri anlık görüntüsü (örn. publishVersion) yakalamanız gerektiğinde kullanışlıdır.

Ancak bunun gerçekleşmesi için, yakalanan değişkenler istemciye gönderilir ve eylem çağrıldığında sunucuya geri gönderilir. Hassas verilerin istemciye ifşa edilmesini önlemek için Next.js, kapalı değişkenleri otomatik olarak şifreler. Bir Next.js uygulaması her oluşturulduğunda her eylem için yeni bir özel anahtar oluşturulur. Bu, eylemlerin yalnızca belirli bir yapı için çağrılabileceği anlamına gelir.

Bilmekte fayda var: Hassas değerlerin istemcide açığa çıkmasını önlemek için yalnızca şifrelemeye güvenmenizi önermiyoruz. Bunun yerine, belirli verilerin istemciye gönderilmesini proaktif olarak önlemek için React taint API'lerini kullanmalısınız.

Overwriting encryption keys (advanced)

Next.js uygulamanızı birden fazla sunucuda kendi kendine barındırırken, her sunucu örneği farklı bir şifreleme anahtarına sahip olabilir ve bu da potansiyel tutarsızlıklara yol açabilir.

Bunu azaltmak için process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ortam değişkenini kullanarak şifreleme anahtarının üzerine yazabilirsiniz. Bu değişkenin belirtilmesi, şifreleme anahtarlarınızın derlemeler arasında kalıcı olmasını ve tüm sunucu örneklerinin aynı anahtarı kullanmasını sağlar.

Bu, birden fazla dağıtımda tutarlı şifreleme davranışının uygulamanız için kritik olduğu gelişmiş bir kullanım durumudur. Anahtar rotasyonu ve imzalama gibi standart güvenlik uygulamalarını göz önünde bulundurmalısınız.

Bilmekte fayda var: Vercel'e dağıtılan Next.js uygulamaları bunu otomatik olarak halleder.

Allowed origins (advanced)

Sunucu Eylemleri bir <form> öğesinde çağrılabildiğinden, bu onları CSRF saldırılarına açar.

Sahne arkasında, Sunucu Eylemleri POST yöntemini kullanır ve yalnızca bu HTTP yönteminin onları çağırmasına izin verilir. Bu, modern tarayıcılardaki CSRF güvenlik açıklarının çoğunu, özellikle de SameSite çerezleri varsayılan olduğunda önler.

Ek bir koruma olarak, Next.js'deki Sunucu Eylemleri ayrıca Origin baş lığını (veya X-Forwarded-Host) Host baş lığıyla karşılaştırır. Bunlar eşleşmezse istek iptal edilir. Başka bir deyişle, Sunucu Eylemleri yalnızca onu barındıran sayfayla aynı ana bilgisayarda çağrılabilir.

Ters proxy veya çok katmanlı arka uç mimarileri (sunucu API'sinin üretim etki alanından farklı olduğu) kullanan büyük uygulamalar için yapılandırma seçeneğinin kullanılması önerilir serverActions.allowedOrigins seçeneği güvenli kökenlerin bir listesini belirtmek için. Bu seçenek bir dizi string kabul eder.

next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
    },
  },
}

Güvenlik ve Sunucu Eylemleri hakkında daha fazla bilgi için .

Additional resources

Sunucu Eylemleri hakkında daha fazla bilgi için aşağıdaki React dokümanlarına göz atın: