Bhavya Dhiman
Bhavya Dhiman

Reputation: 30

Hitting a server action in NextJs using an API for scriping or load testing purposes

Our application is built on NextJs, if we have to perform load testing by calling APIs, how we would be calling server actions using an API itself ?

Calling server actions require a next-action parameter in the request header, and how one can genereate one? Without this parameter, it will redirect to a page.

Here is an image below to see the next-action header.

enter image description here

The server action actually calls the POST method API with the same endpoint as the page.

Sure, we can copy the next-action header from the browser console, but we cannot do it everytime. Also whenever a new build generates, it generates a different hash. So it is very difficult to replicate using an API for load testing or any scripting purposes.

Anyone has an idea how to achieve this?

I tried asking AI, but it was giving wrong answers giving random path in the next module, hence throwing error.

Upvotes: -1

Views: 314

Answers (2)

Ethan
Ethan

Reputation: 1664

TLDR:

You need to obtain and parse the actionId

What are server actions

First let's go in depth on what server action is. In concept it is simple, a server action is a special function within Next.js (and React.js) that runs on the server when some type of user triggered event happens.

In Next.js server action can be used both in server and client components. In a server component you can use it directly. Either way you must define it with the "use server" directive.

Here is an example with server components:

app/page.tsx

export default function Page() {
  // Server Action
  async function create() {
    "use server"
    // Mutate data
  }
 
  return '...'
}

In client components you must define them in a separate file:

actions/actions.ts

'use server'
 
export async function create() {
   // Mutate data
}

To access data from say a form submission you can pass an form submission event to the action as well as action prop to the component:

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>
}

These examples were provided and adapted from the Next.js docs (License MIT)

Sending a request

In the last section we have established a few baselines we need to mock in order to test these actions:

  • The FormData
  • The page of the action
  • Other HTTP headers

To find these let's take a look at how Next.js server actions work under the hood...

To run server actions Next.js generates server endpoints that accept POST requests. So in short here is how I would do it:

  1. Create a setActionId function to that takes in a action and then caches it
  2. Create a getCurrentActionId function that returns the current action id
  3. Create a GET endpoint to return the output of the getCurrentActionId function
  4. Finally, send the POST request like before with the new next-action header as the value returned from this api route

helpers/setActionId

const saveActionId = (actionId: string): void => {
  if(!proccess.env.LOAD_TEST) {
    return;
  }

 if(!fs.existSync(".tests") {
    fs.mkdirSync(".tests");
 }
  
  fs.writeFileSync(".tests/actions", actionId.split("_")[2]); 
}

helpers/getActionId

const getCurrentActionId() => {
  const id = fs.readFileSync(".testsAction");
  fs.rmSync(".tests/actions");
  return id;
}

The only drawback with this approach is you can only test one action at a time.

Hope this helps.

Upvotes: 0

cihanolmalibu
cihanolmalibu

Reputation: 9

You could create a middleware that adds the required next-action header to requests coming from your load testing tool. This would require you to generate and store the correct hash somewhere accessible to the middleware.

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Check if the request is coming from your load testing tool
  if (request.headers.get('X-Load-Test') === 'true') {
    const response = NextResponse.next()
    // Add the next-action header
    // You'll need to generate and store this hash somewhere
    response.headers.set('next-action', 'your-generated-hash')
    return response
  }
  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*',
}

I found this solution hope it works.

Upvotes: -1

Related Questions