<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>M41den ᴜɴᴛʏᴘᴇᴅ</title><generator>teletype.in</generator><description><![CDATA[M41den ᴜɴᴛʏᴘᴇᴅ]]></description><image><url>https://img4.teletype.in/files/fb/71/fb711a68-751b-4364-bccb-b6af79144980.png</url><title>M41den ᴜɴᴛʏᴘᴇᴅ</title><link>https://teletype.in/@m41den</link></image><link>https://teletype.in/@m41den?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=m41den</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/m41den?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/m41den?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Sun, 19 Apr 2026 11:56:57 GMT</pubDate><lastBuildDate>Sun, 19 Apr 2026 11:56:57 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@m41den/nextjs-server-actions-trickery</guid><link>https://teletype.in/@m41den/nextjs-server-actions-trickery?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=m41den</link><comments>https://teletype.in/@m41den/nextjs-server-actions-trickery?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=m41den#comments</comments><dc:creator>m41den</dc:creator><title>Next.js и исключения в Server Action</title><pubDate>Fri, 25 Apr 2025 16:39:20 GMT</pubDate><description><![CDATA[Если вы работали с Server Actions в Next.js, то знаете, насколько это удобно. Вызов серверных функций из клиента без RPC, возможность скрыть часть логики для тупой кнопки, валидация форм на сервере или просто способ скрыть API ключи и внутренний сервис. В общем очень круто... пока вы не начнете выкидывать ошибки, ожидая поймать их в клиенте.]]></description><content:encoded><![CDATA[
  <p id="YKMe">Если вы работали с Server Actions в Next.js, то знаете, насколько это удобно. Вызов серверных функций из клиента без RPC, возможность скрыть часть логики для тупой кнопки, валидация форм на сервере или просто способ скрыть API ключи и внутренний сервис. В общем очень круто... пока вы не начнете выкидывать ошибки, ожидая поймать их в клиенте.</p>
  <p id="eRjJ">Посмотрим на этот кусок кода для входа через Supabase (не забываем <code>&quot;use server&quot;</code>):</p>
  <figure id="rfr8" class="m_column">
    <img src="https://img4.teletype.in/files/35/63/35639f8a-9f3c-43cf-9a5f-3514948d5b84.png" width="1992" />
  </figure>
  <p id="2y4X">Выглядит нормально, наверное на стороне клиента мы ждем что-то типа</p>
  <figure id="CpNd" class="m_column">
    <img src="https://img2.teletype.in/files/56/f2/56f2b6af-1aa3-4782-8045-cd9b70469b57.png" width="852" />
  </figure>
  <p id="UdC1">И это абсолютно правильно, ну по крайней мере в Dev-окружении. Когда вы решите собрать продакшн-сборку, то столкнетесь с очень странным феноменом, от которого <a href="https://github.com/vercel/next.js/issues/71101" target="_blank">полыхает у половины юзеров Next.js</a>:</p>
  <figure id="EHnQ" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/f1/df/f1dfa8fb-7adf-4895-aac9-199c139d97fa.png" width="884" />
    <figcaption>An error occured in the Server Components render. The specific message is ommited in production builds...</figcaption>
  </figure>
  <p id="6CIw">Опа... а где ошибка? Так вот же она, только у нее отобрали трейс и сообщение, оставили только хэш ошибки. Как говорят разрабочики из Vercel, &quot;ето штобы вы дурачки трейсы с ключами апи и паролями клиентам не возвращали&quot;. И даже если вы ловите ошибки в Sentry, а клиенту кидаете что-то типа</p>
  <pre id="8Uci" data-lang="typescript">try {
    ...
} catch (e) {
    sentry.pushEvent(e as Error)
    throw new Error(&quot;Ты дурачок? Ты как нам бэк положил...&quot;)
}</pre>
  <p id="tzFd">то все-равно ваша ошибка будет съедена. Что делать? Передавать ошибки как пропы объектов. То есть теперь все Server Actions должны возвращать что-то вроде</p>
  <pre id="ZMd0" data-lang="typescript">type ActionReturnValue&lt;T&gt; = {
    data?: T,
    error?: Error
}</pre>
  <p id="szzz">Но это ужас, а если мы где-то получим ошибку, где мы ее даже технически не ожидали? А что делать с этим дуализмом, когда наше значение превращается в </p>
  <pre id="MNFU" data-lang="typescript">const {data, error} = await myServerAction(...)
if (e &amp;&amp; e instanceof Error) {
    // Shitty error bruh
    ...
    return
}
const username = data?.username ?? &quot;Unknown&quot; // или data!.username</pre>
  <p id="bw95">Это кринж полный. Однако это единственный выход. Правда можно все красиво оформить...</p>
  <p id="vHmi"></p>
  <p id="rlsB">Вот это копипастите к себе в проект, я сам написал, оно работает отлично и имхо лучше вариантов из гитхаба и статей на Medium:</p>
  <p id="p4Ts"></p>
  <pre id="CJ6z" data-lang="typescript">export class ActionResponse {
    static success&lt;T = unknown&gt;(data: T): ActionData&lt;T&gt; {
        return {
            data
        }
    }

    static error&lt;T = null&gt;(error: Error): ActionData&lt;T&gt; {
        return {
            error: error.message
        }
    }

    static unwrap&lt;T = null&gt;(data: ActionData&lt;T&gt;): T {
        if (data.error)
            throw new Error(data.error)
        return data.data!
    }

    static wrap&lt;P extends (...args: any) =&gt; any&gt;(func: P): ForwardedAsyncFunction&lt;P&gt; {
        return async (...args: ArgumentTypes&lt;P&gt;) =&gt; {
            try {
                return ActionResponse.success(await func(...args))
            } catch (error) {
                return ActionResponse.error(error as Error)
            }
        }
    }
}

export type ActionData&lt;T = unknown&gt; = {
    data?: T,
    error?: string
}


type ForwardedAsyncFunction&lt;F extends (...args: any) =&gt; any&gt; =
    (...args: ArgumentTypes&lt;F&gt;) =&gt; Promise&lt;ActionData&lt;Awaited&lt;ReturnType&lt;F&gt;&gt;&gt;&gt;;


type ArgumentTypes&lt;F extends Function&gt; = 
    F extends (...args: infer A) =&gt; unknown ? A : never;
</pre>
  <p id="P3Vc"></p>
  <p id="J46j">И так наш пример с логином превращается в </p>
  <figure id="ikY9" class="m_column">
    <img src="https://img1.teletype.in/files/c0/56/c056ffc1-256c-4a67-983a-4836f217b0b4.png" width="1816" />
  </figure>
  <figure id="50ON" class="m_column">
    <img src="https://img4.teletype.in/files/f8/00/f8005310-9cb6-43c3-b823-68b7724e619e.png" width="958" />
  </figure>
  <p id="gtI2">Да, unwrap извлекает возвращаемое значение <code>{data}</code> со всей типизацией.</p>
  <p id="dlJZ">Завернуть функцию в Actions и развернуть на клиенте.</p>
  <p id="fm1F"></p>
  <p id="7oei">В принципе все, я просто опять наткнулся на unwrap в своем коде и подумал, что неплохо бы поделиться таким решением, так как я сутки потратил на мучение документации и проблем на гите Next.js - а тут спидран пройден.</p>

]]></content:encoded></item></channel></rss>