search
HomeWeb Front-endJS TutorialServer Actions have been fixed

Server Actions emerged as an idea to reduce client code and simplifying the interactions that require communication with the server. It is an excellent solution that allows developers to write less code. However, there are several challenges associated with its implementation in other frameworks, which should not be overlooked.

In this article, we will talk about these problems and how in Brisa we have found a solution.

Why the need for Server Actions?

To understand what Server Actions provide, it is useful to review how communication with the server used to be. You are probably used to performing the following actions for each interaction with the server:

  1. Capture a browser event (Client)
  2. Normalize and serialize data (Client)
  3. Make a request to the server (Client)
  4. Process the request in an endpoint API (Server)
  5. Respond with the necessary data (Server)
  6. Wait for the response from the server and process it (Client)
  7. Update the data on the client and render the changes (Client)

These seven actions are repeated for each interaction. For example, if you have a page with 10 different interactions, you will repeat a very similar code 10 times, changing only details such as the type of request, the URL, the data sent and the status of the customer.

A familiar example would be
a:

<input oninput="{(e)"> {
    // debounce
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fetch("/api/search", {
        method: "POST",
        body: JSON.stringify({ query: e.target.value }),
      })
        .then((res) => res.json())
        .then((data) => {
          setState({ data });
        });
    }, 300);
  }}
/>

And in the server:

app.post("/api/search", async (req, res) => {
  const { query } = req.body;
  const data = await search(query);
  res.json(data);
});

Increasing the client bundle size... and the frustration of developers.

Server Actions have been fixed


Frustrated Developer

How Server Actions work

Server Actions encapsulate these actions in a Remote Procedure Call (RPC), which manages the client-server communication, reducing the code on the client and centralizing the logic on the server:

  1. Capture a browser event (RPC Client)
  2. Normalize and serialize data (RPC Client)
  3. Make a request to the RPC server (RPC Client)
  4. Execute the action on the server with the data (RPC Server)
  5. Option 1:
  • Render from the server and send streaming to the client (RPC Server)
  • Process the chunks of the stream so that the changes are visible (RPC Client)
  1. Option 2:
  • Reply with the necessary data and transfer properties from the server store to the client store (RPC Server)
  • Make the signals that were listening to the changes react to the changes in the store (RPC Client)

Here everything is done for you by the Brisa RPC.

Server Actions have been fixed


Remote Procedure Call

This would be the code from a server component:

<input oninput="{(e)"> {
    // debounce
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fetch("/api/search", {
        method: "POST",
        body: JSON.stringify({ query: e.target.value }),
      })
        .then((res) => res.json())
        .then((data) => {
          setState({ data });
        });
    }, 300);
  }}
/>

Here, developers do not write client code, since it is a server component. The onInput event is received after the debounce, handled by the Client RPC, while the Server RPC uses "Action Signals" to trigger the Web Components that have signals registered with that store property.

As you can see, this significantly reduces the server code and, best of all, the code size on the client does not increase with each interaction. The RPC Client code occupies a fixed 2 KB, whether you have 10 or 1000 such interactions. This means that increase 0 bytes in the client bundle size, with other words, doesn't increase.

Server Actions have been fixed


0 bytes on client bundle size

Moreover, in the case of needing a rerender, this is done on the server and is returned in HTML streaming, making the user see the changes much earlier than in the traditional way where you had to do this work on the client after the server response.

In this way:

  • Improve the user experience (UX)
  • Improve the development experience (DX)

Server Actions have been fixed


Happy Developer

Differences between Brisa Server Actions and other frameworks

1. Numbers of events to capture

In other frameworks such as React, they have focused on actions only being part of the form onSubmit, instead of any event.

This is a problem, since there are many non-form events that should also be handled from a server component without adding client code. For example, an onInput of an input to do automatic suggestions, an onScroll to load an infinite scroll, an onMouseOver to do a hover, etc.

Server Actions have been fixed


Applications are more interactive than expected

2. Having more HTML controls over Server Actions

Many frameworks have also seen the HTMX library as a very different alternative to server actions, when in fact it has brought very good ideas that can be combined with Server Actions to have more potential by simply adding extra attributes in the HTML that the RPC Client can take into account, such as the debounceInput that we have seen before. Also other HTMX ideas like the indicator to show a spinner while making the request, or being able to handle an error in the RPC Client.

Server Actions have been fixed


HTMX ideas

3. Separation of concerns

When Server Actions were introduced in React, there was a new paradigm shift that many developers had to change the mental chip when working with them.

We wanted to make it as familiar as possible to the Web Platform, this way, you can capture the serialized event from the server and use its properties. The only event a little different is the onSubmit that has already transferred the FormData and has the e.formData property, nevertheless, the rest of event properties are interactable. This is an example resetting a form:

<input oninput="{(e)"> {
    // debounce
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fetch("/api/search", {
        method: "POST",
        body: JSON.stringify({ query: e.target.value }),
      })
        .then((res) => res.json())
        .then((data) => {
          setState({ data });
        });
    }, 300);
  }}
/>

In this example, there is no client code at all and during the server action you can disable the submit button with the indicator, using CSS, so that the form cannot be submitted twice, and at the same time after doing the action on the server and access the form data with e.formData and then resetting the form using the same API of the event.

Mentally, it is very similar to working with the Web Platform. The only difference is that all the events of all the server components are server actions.

This way, there is a real separation of concerns, where it is NOT necessary to put "user server" or "use client" in your components anymore.

Just keep in mind that everything runs only on the server. The only exception is for the src/web-components folder which runs on the client and there the events are normal.

Server Actions have been fixed


Two different worlds, but in agreement

4. Event Propagation

In Brisa, the Server Actions are propagated between Server Components as if they were DOM events. That is to say, from a Server Action you can call an event of a prop of a Server Component and then the Server Action of the parent Server Component is executed, etc.

<input oninput="{(e)"> {
    // debounce
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fetch("/api/search", {
        method: "POST",
        body: JSON.stringify({ query: e.target.value }),
      })
        .then((res) => res.json())
        .then((data) => {
          setState({ data });
        });
    }, 300);
  }}
/>

In this case, the onAfterMyAction event is executed on the parent component and an action can be done on the server. This is very useful to make actions on the server that effect several server components.

Server Actions have been fixed


Propagate action

4. Comunication between both worlds

Especially after the last few weeks Web Components have been a bit frowned upon after several discussions on X (formelly Twitter). However, being part of the HTML, it is the best way to interact with Server Actions for several reasons:

  1. You can capture any Web Component event from the server and generate client-server communication. Example . This is very powerful, since all the events inside the Web Component is only client logic without putting any server logic there, simply from the server when consuming the Web Component you can do server actions.
  2. The HTTP protocol can be used for what it was designed for, to transfer Hypertext (HTML) in streaming, this way if after a re-rendering from a Server Action any attribute of a Web Component is updated, the diffing algorithm of the RPC Client makes the Web Component to be updated without much effort. The Web Components attributes in Brisa are signals that make the internal parts of Web Component react without having to rerender. This process in other frameworks becomes very complicated, making the RPC server have to process JSON or JS over the wire, instead of HTML, which makes the streaming implementation more complicated.

Using attributes in Web Components requires serialization in the same way as transmitting data from server to client without using Web Components, therefore, using both, there is no extra serialization to manage.

Note: Streaming HTML and processing it with the diffing algorithm is something I explained in this other article if you are interested.

Server Actions have been fixed


Hypertext in streaming over the wire

5. New concept: Action Signals

In Brisa, we have added a new concept to give even more power to the Server Actions, this concept is called "Action Signals". The idea of the "Action Signals" is that you have 2 stores, one on the server and one on the client.

Why 2 stores?

The default server store lives only at the request level. And you can share data that will not be visible to the client. For example you can have the middleware set the user and have access to sensitive user data in any Server Component. By living at request level it is impossible to have conflicts between different requests, since each request has its own store and is NOT stored in any database, when the request is finished, it dies by default.

On the other hand, in the client store, it is a store that each property when consumed is a signal, that is to say, if it is updated, the Web Component that was listening to that signal reacts.

However, the new concept of "Action Signal" is that we can extend the life of the server store beyond the request. To do this it is necessary to use this code:

<input oninput="{(e)"> {
    // debounce
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fetch("/api/search", {
        method: "POST",
        body: JSON.stringify({ query: e.target.value }),
      })
        .then((res) => res.json())
        .then((data) => {
          setState({ data });
        });
    }, 300);
  }}
/>

This transferToClient method, share server data to the client store and converted into signals. In this way, many times it will not be necessary to make any re-rendering from the server, you can simply from a Server Action make react the signals of the Web Components that were listening to that signal.

This store transfer makes the life of the server store now:

Render initial Server Component → Client → Server Action → Client → Server Action...

So it goes from living from only at request level to live permanently, compatible with navigation between pages.

Server Actions have been fixed


Share data between both worlds (server/client)

Example:

app.post("/api/search", async (req, res) => {
  const { query } = req.body;
  const data = await search(query);
  res.json(data);
});

In this example, we extend the life of the errors store property, not to be used on the client, but to be reused in the Server Action and then finally in the rerender of the Server Action. In this case, being a non-sensitive data, it is not necessary to encrypt it. This example code all happens on the server, even the rerendering and the user will see the errors after this rendering on the server where the Server RPC will send the HTML chunks in streaming and the Client RPC will process it to make the diffing and show the errors to give feedback to the user.

6. Encrypt only the sensitive data

If within a server action some variable is used that existed at render level, at security level many frameworks like Next.js 14 what they do is to encrypt this data to create an snapshot of data used at the time of rendering. This is more or less fine, but encrypting data always has an associated computational cost and it is not always sensitive data.

In Brisa, to solve this, there are different requests, where in the initial render it has a value, and in the server action you can capture the value that it has in this request.

<input debounceinput="{300}" oninput="{async"> {
    // All this code only runs on the server
    const data = await search(e.target.value);
    store.set("query", data);
    store.transferToClient(["query"]);
  }}
/>

This is useful in some cases but not always, for example if you do a Math.random it will be different between the initial render and the Server Action execution for sure.

<input oninput="{(e)"> {
    // debounce
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      fetch("/api/search", {
        method: "POST",
        body: JSON.stringify({ query: e.target.value }),
      })
        .then((res) => res.json())
        .then((data) => {
          setState({ data });
        });
    }, 300);
  }}
/>

This is why we created the concept of "Action Signals", to transfer data from the server store to the client store, and the developer can decide whether to encrypt it or not at will.

Sometimes, instead of querying the database from the Server Action, you may want to transfer data that already exists in the initial render even if it requires an associated encryption. To do this, you simply use:

app.post("/api/search", async (req, res) => {
  const { query } = req.body;
  const data = await search(query);
  res.json(data);
});

When you do:

<input debounceinput="{300}" oninput="{async"> {
    // All this code only runs on the server
    const data = await search(e.target.value);
    store.set("query", data);
    store.transferToClient(["query"]);
  }}
/>

Inside a Web Component (client) will always be encrypted, but on the server it will always be decrypted.

Note: Brisa uses aes-256-cbc for encryption, a combination of cryptographic algorithms used to securely encrypt information recommended by OpenSSL. Encryption keys are generated during the build of your project.

Server Actions have been fixed


Share encrypted data between both worlds (server/client)

Conclusion

In Brisa, although we like to support writing Web Components easily, the goal is to be able to make a SPA without client code and only use Web Components when it is a purely client interaction or the Web API has to be touched. That's why Server Actions are so important, as they allow interactions with the server without having to write client code.

We encourage you to try Brisa, you just have to run this command in the terminal: bun create brisa, or try some example to see how it works.

References

  • Server Actions Convetion
  • Server Actions Behavior
  • Forms with Server Actions
  • Nested Actions
  • Server-side validation and error handling
  • Debounce a Server Action
  • Optimistic Updates
  • Re-render in Action
  • Navigate to another page with Server Actions
  • Access to Cookies
  • Security in Server Actions
  • Action Signals
  • Transfer sensitive data
  • Props in Server Actions
  • Using Server Actions in a Reverse Proxy

The above is the detailed content of Server Actions have been fixed. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Javascript Data Types : Is there any difference between Browser and NodeJs?Javascript Data Types : Is there any difference between Browser and NodeJs?May 14, 2025 am 12:15 AM

JavaScript core data types are consistent in browsers and Node.js, but are handled differently from the extra types. 1) The global object is window in the browser and global in Node.js. 2) Node.js' unique Buffer object, used to process binary data. 3) There are also differences in performance and time processing, and the code needs to be adjusted according to the environment.

JavaScript Comments: A Guide to Using // and /* */JavaScript Comments: A Guide to Using // and /* */May 13, 2025 pm 03:49 PM

JavaScriptusestwotypesofcomments:single-line(//)andmulti-line(//).1)Use//forquicknotesorsingle-lineexplanations.2)Use//forlongerexplanationsorcommentingoutblocksofcode.Commentsshouldexplainthe'why',notthe'what',andbeplacedabovetherelevantcodeforclari

Python vs. JavaScript: A Comparative Analysis for DevelopersPython vs. JavaScript: A Comparative Analysis for DevelopersMay 09, 2025 am 12:22 AM

The main difference between Python and JavaScript is the type system and application scenarios. 1. Python uses dynamic types, suitable for scientific computing and data analysis. 2. JavaScript adopts weak types and is widely used in front-end and full-stack development. The two have their own advantages in asynchronous programming and performance optimization, and should be decided according to project requirements when choosing.

Python vs. JavaScript: Choosing the Right Tool for the JobPython vs. JavaScript: Choosing the Right Tool for the JobMay 08, 2025 am 12:10 AM

Whether to choose Python or JavaScript depends on the project type: 1) Choose Python for data science and automation tasks; 2) Choose JavaScript for front-end and full-stack development. Python is favored for its powerful library in data processing and automation, while JavaScript is indispensable for its advantages in web interaction and full-stack development.

Python and JavaScript: Understanding the Strengths of EachPython and JavaScript: Understanding the Strengths of EachMay 06, 2025 am 12:15 AM

Python and JavaScript each have their own advantages, and the choice depends on project needs and personal preferences. 1. Python is easy to learn, with concise syntax, suitable for data science and back-end development, but has a slow execution speed. 2. JavaScript is everywhere in front-end development and has strong asynchronous programming capabilities. Node.js makes it suitable for full-stack development, but the syntax may be complex and error-prone.

JavaScript's Core: Is It Built on C or C  ?JavaScript's Core: Is It Built on C or C ?May 05, 2025 am 12:07 AM

JavaScriptisnotbuiltonCorC ;it'saninterpretedlanguagethatrunsonenginesoftenwritteninC .1)JavaScriptwasdesignedasalightweight,interpretedlanguageforwebbrowsers.2)EnginesevolvedfromsimpleinterpreterstoJITcompilers,typicallyinC ,improvingperformance.

JavaScript Applications: From Front-End to Back-EndJavaScript Applications: From Front-End to Back-EndMay 04, 2025 am 12:12 AM

JavaScript can be used for front-end and back-end development. The front-end enhances the user experience through DOM operations, and the back-end handles server tasks through Node.js. 1. Front-end example: Change the content of the web page text. 2. Backend example: Create a Node.js server.

Python vs. JavaScript: Which Language Should You Learn?Python vs. JavaScript: Which Language Should You Learn?May 03, 2025 am 12:10 AM

Choosing Python or JavaScript should be based on career development, learning curve and ecosystem: 1) Career development: Python is suitable for data science and back-end development, while JavaScript is suitable for front-end and full-stack development. 2) Learning curve: Python syntax is concise and suitable for beginners; JavaScript syntax is flexible. 3) Ecosystem: Python has rich scientific computing libraries, and JavaScript has a powerful front-end framework.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

SublimeText3 English version

SublimeText3 English version

Recommended: Win version, supports code prompts!

PhpStorm Mac version

PhpStorm Mac version

The latest (2018.2.1) professional PHP integrated development tool

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

WebStorm Mac version

WebStorm Mac version

Useful JavaScript development tools