4 min read

A Bit of SaaS Weekly: Are magic features bad?

A Bit of SaaS Weekly: Are magic features bad?

This is a weekly newsletter on the Software as a Service world. Learning, building, and shipping. Written by Ethan Mick.

When designing a conference, have the keynote first. Then, have in-depth talks after the keynote going over the features you announced. Don't do it the other way around. That just makes no sense.

Vercel, I'm looking at you.

The Best Bits


When frameworks become magical

Next.js 13.4 has been released, and that app directory is ready for production. One of the things Vercel had been sitting on was figuring out a good way to handle mutating server data in the app directory.

Traditionally, for any web app, fetching data has been straightforward. Either the server can fetch the data and use it to render the page server-side, or the client requests the data with JavaScript and then updates the DOM.

Mutating data on the server (such as creating, updating, or deleting a resource) has also been straightforward if verbose. You add an API that accepts the request and then mutates the state in the handler. To trigger this, you make the request from the client when the user does an action.

The server side might look like this (Next.js route handler):

export async function POST(req: Request) {
  try {
    const { email, password } = await req.json()
    const hashed = await hash(password, 12)

    const user = await prisma.user.create({
      data: {
        password: hashed

    return NextResponse.json({
      user: {
        email: user.email
  } catch (err: any) {
    return new NextResponse(
        error: err.message
        status: 500

The client side is also quite verbose (React example):

const onSubmit = async (e: React.FormEvent) => {

    try {
      const res = await fetch('/api/register', {
        method: 'POST',
        body: JSON.stringify({
        headers: {
          'Content-Type': 'application/json'
      if (res.ok) {
      } else {
        setError((await res.json()).error)
    } catch (error: any) {
  // Later
  <form onSubmit={onSubmit} />

That's an example of registering a user when they sign up.

Various frameworks have tried to make this easier. Remix, for example. And now, Next.js has created a new way to do this with Server Actions. The above looks like this with the new API:

async function register(data: FormData) {
    'use server'
    const hashed = await hash(data.get('password'), 12)
    await prisma.user.create({
      data: {
        email: data.get('email'),
        password: hashed

<form action={register} />

That's it. That will create the user and then redirect them to the login page to login. No more API you need to make. No more long complicated form handler.

It's just magic.

But my question is... is that good?

I think writing less code is good. Developers spend way too much time writing boilerplate code. But as frameworks do more, they become more magical, and the developer has a harder time understanding what's happening under the hood. Next.js had resisted that urge for a long time.

To me, this crosses a threshold.

We're entering into "magic framework" territory.

Ruby on Rails has been like this from the beginning. It's not a bad thing; it's just a different thing. Someone new to Next.js will have a hard time looking at that and knowing what exactly is going on. They're going to have questions:

  • How does the data get transferred?
  • How does it handle errors on the server?
  • How does it inform the user of errors?
  • How can it show the loading state while the asynchronous action occurs?
  • Where can I use this? Where can't I use it?

The answers to these questions are not obvious. For example, you can only define a server action in a server component. But you can use a server action in a client component... if it was defined elsewhere.

To deeply understand what's happening, developers are going to need to become more versed in the specifics of what Next.js is doing. It's not easily transferred to other frameworks or languages. The gap between a basic understanding of Next.js and an expert one will grow as these features become more common.

It'll be an interesting change, that's for sure. With the app directory now blessed for production, get ready for lots of questions about this topic.

Learn to build SaaS

I finished the second video on creating a button component, adding in variants and a loading state. This is a great one to watch and build and then use in your projects. I'm going to do one more on some very advanced bug fixes, but I think this is where most people should stop. Enjoy!


Colors of Tailwind for a quick reference to Tailwind colors.

Tech Tip

In TypeScript, you sometimes have a Class you want to create, but the library author did not provide types for the class or the constructor arguments. You can actually get those with a helper TypeScript method:

type ConstructorArgs = ConstructorParameters<typeof Library.Class>

If the constructor has multiple arguments and you only want one of them, you can use an indexed type:

type ArgZero = ConstructorParameters<typeof Library.Class>[0]

Go forth and type!

Cloud Chronicles

  • YouTube Subscribers: 943 (+82 in the last 7 days)
  • Newsletter Members: 151 (+12 in the last 7 days)

I'm finishing up my project with my old company, and we are starting to work on a second statement of work for phase 2. I'm not sure what the timeline is for getting that started. There is a good chance it's put on the back burner for a while, which is fine by me.

I think, at this point, I'd prefer to have one large client at a time and then one smaller one. When I have two large clients, I feel like I'm not doing either one justice or spending enough time with them.

Also, I want to spend more time on content, and I've been super busy lately with freelancing. But to break out of the need to freelance... I need to spend more time on content. It's hard to balance.

Last Byte