Kevin Amiranoff
Kevin Amiranoff

Reputation: 14503

Intermittent 405 Method not allowed with Next.js/Vercel and server action

We tried to implement server actions in Next.js 14.2.13 deployed in Vercel but we get intermittent 405 status code every 4/5 calls

Here is the function

'use server';

import {
  CreateNoteFormDataSchema,
  NoteFormInputs,
} from '@/src/features/Notes/CreateNoteInDeck/NoteForm/createNoteSchema';
import axios from 'axios';
import { BASE_URL } from '@/src/app/api/Api';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/src/app/api/auth/[...nextauth]/authOptions';
import { getHeaders } from '@/src/app/api/getHeaders';
import { sanitizeHTML } from '@/src/app/api/utils/sanitizeHTML';
import { FontTypes } from '@/src/features/Notes/utils/FontFamilyConfig';

type CreateNoteReqBody = {
  title: string;
  body: string;
  deckId?: number | null;
  tags: string[];
  pattern: string | null;
  backgroundColor: string | null;
  fontFamily: string | null;
  visibility: 'public' | 'private' | 'friends';
};

type CreateNoteResBody = {
  noteId: string;
  deckId: number | null;
  title: string;
  body: string;
  bodySummary: string;
  tags: string[];
  pattern: string | null;
  backgroundColor: string | null;
  fontFamily: string | null;
  visibility: 'public' | 'private' | 'friends';
  updatedAt: string;
};

export type CreateNoteServerActionState = {
  success: boolean;
  error: string | null;
  data: CreateNoteResBody | null;
};

export async function _createNoteServerAction(data: NoteFormInputs): Promise<CreateNoteServerActionState> {
  try {
    const parsedData = CreateNoteFormDataSchema.parse({
      deckId: data.deckId,
      backgroundColor: data.backgroundColor,
      fontType: data.fontType,
      visibility: data.visibility,
      title: sanitizeHTML(data.title),
      body: sanitizeHTML(data.body),
      tags: data.tags?.map((tag) => sanitizeHTML(tag)) || [],
    });

    const validatedData: CreateNoteReqBody = {
      deckId: parsedData.deckId || null,
      title: parsedData.title,
      body: parsedData.body,
      backgroundColor: parsedData.backgroundColor || null,
      visibility: parsedData.visibility,
      fontFamily: data.fontType || FontTypes[0],
      pattern: null,
      tags: parsedData.tags || [],
    };

    const session = await getServerSession(authOptions);
    const config = getHeaders(session?.accessToken);

    const response = await axios.post<CreateNoteResBody>(`${EXTERNAL_API_BASE_URL}/note`, validatedData, config);

    return { success: true, data: response.data, error: null };
  } catch (error) {
    console.error('createNoteServerAction', error);
    return { success: false, error: 'Something went wrong', data: null };
  }
}

And it is called from here:


 const {
    register,
    handleSubmit,
    control,
    formState: { errors },
    watch,
    setValue,
  } = useForm<NoteFormInputs>({
    defaultValues: {
      deckId,
      title: defaultNote?.title || '',
      body: defaultNote?.body || '',
      backgroundColor: defaultNote?.backgroundColor || '',
      pattern: defaultNote?.pattern || '',
      fontType: defaultNote?.font.name || FontTypes[0],
      visibility: defaultNote?.visibility || 'public',
      tags: defaultNote?.tags || [],
    },
    resolver: zodResolver(CreateNoteFormDataSchema),
  });

// ....

const onSubmit: SubmitHandler<NoteFormInputs> = useCallback(
    (data) => {
      startTransition(async () => {
        if (defaultNote?.id) {
          const editResult = await _editNoteServerAction(defaultNote.id, data);

          if (editResult.success && editResult.data) {
            BBToast.success('Note updated');
            return;
          }
          BBToast.error('Failed to update note');
          return;
        }

        const result = await _createNoteServerAction(data);

        if (result.success && result.data) {
          BBToast.success('Note created');
          return router.replace(`${pathName}/../${result.data.noteId}`);
        }
        BBToast.error('Failed to create note');
        return;
      });
    },
    [defaultNote?.id, pathName, router],
  );

// ....
    <form onSubmit={handleSubmit(onSubmit)} className="p-4 h-full">

the logs from Vercel is a 500 enter image description here

And on the client side chrome console a 405 enter image description here

Any help, idea, of explanation of why this is happening would be greatly appreciated!

Upvotes: 1

Views: 55

Answers (1)

TOPKAT
TOPKAT

Reputation: 8678

In my case this was due to a wrong Content-Security-Policy set in the header which didn't allow certains external ressources to access my site.

I just needed to change a config in the vercel.json file:

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  ...
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        ...  
        // ⬇ to be updated here ⬇
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self' THIRD_PARTY_API_URL_1 THIRD_PARTY_API_URL_2; style-src 'self' 'unsafe-inline'; img-src IMAGE_PROVIDER_URL 'self' data:; connect-src 'self' BACKEND_URL THIRD_PARTY_API_URL; frame-src 'self' EXTERNAL_I_FRAME_URL"
        }
        ...
      ]
    }
  ]
}

where you shall update *_URL variables with one of your authorized urls (accept wilcards)

You can generate content-security-Policies online here

Upvotes: 1

Related Questions