#cookieStore.get is not a function

1 messages · Page 1 of 1 (latest)

heavy jackal
#

// src/lib/supabaseServer.js
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export function createSupabaseServer() {
const cookieStore = cookies()

return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
get: (name) => cookieStore.get(name)?.value,
set: (name, value, options) =>
cookieStore.set({ name, value, ...options }),
remove: (name, options) =>
cookieStore.set({ name, value: '', ...options }),
},
}
)
}

cookieStore.get is not a function, what do I have to do about this?

glossy rapidsBOT
#

Hi @heavy jackal. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

The issue is that in Next.js 15, cookies() from next/headers is now an async function that returns a Promise. You're calling it synchronously, so cookieStore is a Promise object — not the cookie store itself — which is why .get is not a function. [async APIs upgrade]

You need to make createSupabaseServer an async function and await the cookies() call:

// src/lib/supabaseServer.js
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createSupabaseServer() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
    {
      cookies: {
        get: (name) => cookieStore.get(name)?.value,
        set: (name, value, options) =>
          cookieStore.set({ name, value, ...options }),
        remove: (name, options) =>
          cookieStore.set({ name, value: '', ...options }),
      },
    }
  )
}

Then wherever you call createSupabaseServer(), you'll also need to await it:

const supabase = await createSupabaseServer()

Key points to keep in mind: [cookies reference]

  • cookies() is asynchronous in Next.js 15 and must be awaited.
  • In version 14 and earlier it was synchronous, which is why your original code may have worked before.
  • Cookie writes (.set, .delete) are only allowed in Server Actions or Route Handlers, not in plain Server Components.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids Okay now:

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'

export async function signUp(formData) {
const supabase = createSupabaseServer()

const name = formData.get('name')
const email = formData.get('email')
const password = formData.get('password')

const { data, error } = await supabase.auth.signUp({
email,
password,
})

if (error) {
return { error: error.message }
}

await supabase.from('owners').insert({
id: data.user.id,
name,
email,
location: '',
})

return { success: true }
}

Error Type

Runtime TypeError

Error Message

Cannot read properties of undefined (reading 'signUp')

at signUp (src\lib\auth.js:13:47)
at form (<anonymous>:null:null)
at Page (src\app\(login)\sign-up\page.js:26:13)

Code Frame

11 | const password = formData.get('password')
12 |

13 | const { data, error } = await supabase.auth.signUp({
| ^
14 | email,
15 | password,
16 | })

Next.js version: 16.2.2 (Turbopack)

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

The problem is the same as before — you forgot to await the createSupabaseServer() call. Since it's now an async function, calling it without await gives you a Promise, not the Supabase client. That's why supabase.auth is undefined.

Fix it by awaiting the call:

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'

export async function signUp(formData) {
  const supabase = await createSupabaseServer() // <-- add await here

  const name = formData.get('name')
  const email = formData.get('email')
  const password = formData.get('password')

  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  })

  if (error) {
    return { error: error.message }
  }

  await supabase.from('owners').insert({
    id: data.user.id,
    name,
    email,
    location: '',
  })

  return { success: true }
}

Anywhere you call createSupabaseServer(), you must await it, since it internally uses await cookies() which is an async API in Next.js 15+. [async APIs upgrade]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids This is my database table:

create table public.owners ( id bigint generated by default as identity not null, name character varying not null, email character varying not null, profile_photo character varying null, location character varying not null, created_at timestamp with time zone not null default now(), description text null, auth_id uuid null, constraint owners_pkey primary key (id), constraint owners_auth_id_fkey foreign KEY (auth_id) references auth.users (id) on delete CASCADE ) TABLESPACE pg_default;

Please make sure it works correctly with this, because I had a Auth made but it didn't add to the owners table.

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Looking at your table schema, the issue is the mismatch between what your code inserts and what the table expects. Your table uses:

  • id — a bigint auto-generated identity (not a UUID)
  • auth_id — a uuid column that references auth.users(id)

But your current signUp code does this:

await supabase.from('owners').insert({
  id: data.user.id,  // ❌ This is a UUID, but `id` is a bigint identity
  name,
  email,
  location: '',
})

You're inserting the Supabase auth UUID into the id column, which is a bigint — that will fail or behave incorrectly. You should instead insert the UUID into auth_id and let the id auto-generate.

Here's the corrected signUp function:

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'

export async function signUp(formData) {
  const supabase = await createSupabaseServer()

  const name = formData.get('name')
  const email = formData.get('email')
  const password = formData.get('password')

  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  })

  if (error) {
    return { error: error.message }
  }

  const { error: insertError } = await supabase.from('owners').insert({
    auth_id: data.user.id, // ✅ UUID goes into auth_id
    name,
    email,
    location: '',
    // `id` is auto-generated, do not insert it manually
  })

  if (insertError) {
    return { error: insertError.message }
  }

  return { success: true }
}

Key changes:

  • Use auth_id: data.user.id instead of id: data.user.id — the auth UUID belongs in auth_id.
  • Remove id from the insert entirely — it's a bigint generated by default as identity, so Postgres will auto-assign it.
  • Added error handling for the insert call so you can catch and surface any database errors (e.g. missing required fields, constraint violations).
#

Also make sure description and profile_photo are nullable in your schema (they are), so omitting them from the insert is fine. [use server docs]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids Can you please add logs for debugging, there's no account added for the table and also no account is added in the Authentication

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Here's the updated signUp function with console.log statements added for debugging, so you can trace exactly where things might be failing:

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'

export async function signUp(formData) {
  console.log('[signUp] Starting sign up process...')

  const supabase = await createSupabaseServer()
  console.log('[signUp] Supabase client created:', !!supabase)

  const name = formData.get('name')
  const email = formData.get('email')
  const password = formData.get('password')

  console.log('[signUp] Form data received:', { name, email, password: password ? '***' : 'MISSING' })

  if (!name || !email || !password) {
    console.error('[signUp] Missing required fields')
    return { error: 'Name, email, and password are required.' }
  }

  console.log('[signUp] Calling supabase.auth.signUp...')
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  })

  console.log('[signUp] Auth signUp result:', { data, error })

  if (error) {
    console.error('[signUp] Auth error:', error.message)
    return { error: error.message }
  }

  if (!data?.user) {
    console.error('[signUp] No user returned from auth.signUp')
    return { error: 'No user was created. The email may already be registered.' }
  }

  console.log('[signUp] Auth user created with id:', data.user.id)
  console.log('[signUp] Inserting into owners table...')

  const { data: insertData, error: insertError } = await supabase
    .from('owners')
    .insert({
      auth_id: data.user.id,
      name,
      email,
      location: '',
    })
    .select()

  console.log('[signUp] Insert result:', { insertData, insertError })

  if (insertError) {
    console.error('[signUp] Insert error:', insertError.message)
    return { error: insertError.message }
  }
#

console.log('[signUp] Owner record created successfully:', insertData)
return { success: true }
}


**What to look for in the logs:**

- If `data.user` is `null` after `signUp`, Supabase may require **email confirmation** before the user is fully created. In that case, the insert would fail because there's no user ID yet.
- If `insertError` appears, the message will tell you exactly what constraint or field is causing the failure (e.g. a missing required field or a foreign key violation).
- The `.select()` added to the insert call will return the inserted row so you can confirm it was written correctly.

Since these are Server Actions, the logs will appear in your **terminal/server console**, not the browser. [[Vercel Function Logs](<https://vercel.com/docs/functions/logs#vercel-function-logs>)]

> **Note on email confirmation:** If Supabase has email confirmation enabled, `data.user` may be returned but `data.user.identities` may be empty, or the user won't be confirmed yet. You may want to check `data.user.identities?.length === 0` as a sign the email is already registered.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids

[signUp] Starting sign up process...
[signUp] Supabase client created: true
[signUp] Form data received: { name: 'Test', email: 'gmail@gmail.com', password: '***' }
[signUp] Calling supabase.auth.signUp...
[signUp] Auth signUp result: {
data: { user: null, session: null },
error: Error [AuthApiError]: email rate limit exceeded
at async signUp (src\lib\auth.js:24:27)
22 |
23 | console.log('[signUp] Calling supabase.auth.signUp...')

24 | const { data, error } = await supabase.auth.signUp({
| ^
25 | email,
26 | password,
27 | }) {
__isAuthError: true,
status: 429,
code: 'over_email_send_rate_limit'
}
}
[signUp] Auth error: email rate limit exceeded
POST /sign-up 200 in 883ms (next.js: 11ms, application-code: 872ms)
└─ ƒ signUp({}) in 809ms src/lib/auth.js

I use supabase

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

The error over_email_send_rate_limit (HTTP 429) is coming from Supabase, not Next.js. Supabase's free tier enforces a strict email sending rate limit (typically 2–3 emails per hour on the free plan).

This is a Supabase-side restriction, not something you can fix in your Next.js code. However, here are your options:

1. Wait and retry
Simply wait an hour before trying to sign up again with the same or a different email.

2. Disable email confirmation in Supabase (for development)
In your Supabase dashboard:

  • Go to Authentication → Providers → Email
  • Disable "Confirm email"

This way, sign-ups won't trigger a confirmation email and you won't hit the rate limit during development.

3. Use a custom SMTP provider
In your Supabase dashboard:

  • Go to Project Settings → Auth → SMTP Settings
  • Configure your own SMTP provider (e.g. Resend, SendGrid, Postmark)

This removes the Supabase email rate limit entirely.

4. Add rate limiting on your Next.js side
To prevent users from hitting this Supabase limit in production, you can add rate limiting to your /sign-up endpoint. For example, using Vercel's WAF you can limit requests to auth endpoints to prevent abuse: [limit auth abuse]

// Example: limit to 10 requests per 60s per IP on /api/auth/signup

Or use the @vercel/firewall package for code-level rate limiting: [add rate limiting]

import { checkRateLimit } from '@vercel/firewall';

export async function signUp(formData) {
  const { rateLimited } = await checkRateLimit('signup_rate_limit', { request });
  if (rateLimited) {
    return { error: 'Too many sign-up attempts. Please try again later.' };
  }
  // ... rest of your signUp logic
}
#

For now, the quickest fix for development is to disable email confirmation in your Supabase dashboard.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids This works perfectly fine now, how can I do the login now?

// Default template
import Input from "@/components/input"
import Image from 'next/image'
import Button from "@/components/button"
export default function Page() {
return (
<main className="flex-1">
<div className="card bg-white shadow-xl p-4 rounded-lg">
<div className="flex flex-col items-center justify-center mb-4">
<Image
src="/logo.webp"
alt="PawMatch Logo"
width={96}
height={96}
priority
/>
<h5 className="text-h5 font-display text-center mt-4">
Welcome to PawMatch
</h5>
<p className="text-center opacity-50 mt-2">
Sign in to find your dog's perfect playmate(s)
</p>
</div>
<form className="space-y-2">
<Input
id="email"
label="Email Address"
type="email"
placeholder="example@email.com"
/>
<Input
id="password"
label="Password"
type="password"
placeholder="*********"
/>
<Button size="lg" variant="primary" className="w-full mt-2">
Sign in
</Button>
<div className="text-center text-text">
Don't have an account? <a href="/sign-up" className="text-primary font-medium">Sign up</a>
</div>
</form>
</div>
</main>
)
}

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the pattern established in your signUp function, here's how to implement login. You'll need a Server Action for the login logic and then wire it up to your form.

1. Add a signIn Server Action to src/lib/auth.js:

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'
import { redirect } from 'next/navigation'

// ... your existing signUp function ...

export async function signIn(formData) {
  console.log('[signIn] Starting sign in process...')

  const supabase = await createSupabaseServer()

  const email = formData.get('email')
  const password = formData.get('password')

  console.log('[signIn] Form data received:', { email, password: password ? '***' : 'MISSING' })

  if (!email || !password) {
    console.error('[signIn] Missing required fields')
    return { error: 'Email and password are required.' }
  }

  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  })

  console.log('[signIn] Auth signIn result:', { data, error })

  if (error) {
    console.error('[signIn] Auth error:', error.message)
    return { error: error.message }
  }

  console.log('[signIn] Sign in successful for user:', data.user.id)
  redirect('/dashboard') // change to your desired route
}

2. Update your login page to use the Server Action:

// app/(login)/sign-in/page.js
'use client'

import Input from "@/components/input"
import Image from 'next/image'
import Button from "@/components/button"
import { signIn } from '@/lib/auth'
import { useActionState } from 'react'

export default function Page() {
  const [state, action, pending] = useActionState(signIn, undefined)
#

return (
<main className="flex-1">
<div className="card bg-white shadow-xl p-4 rounded-lg">
<div className="flex flex-col items-center justify-center mb-4">
<Image
src="/logo.webp"
alt="PawMatch Logo"
width={96}
height={96}
priority
/>
<h5 className="text-h5 font-display text-center mt-4">
Welcome to PawMatch
</h5>
<p className="text-center opacity-50 mt-2">
Sign in to find your dog's perfect playmate(s)
</p>
</div>
<form action={action} className="space-y-2">
<Input
id="email"
name="email"
label="Email Address"
type="email"
placeholder="example@email.com"
/>
<Input
id="password"
name="password"
label="Password"
type="password"
placeholder="*********"
/>
{state?.error && (
<p className="text-red-500 text-sm">{state.error}</p>
)}
<Button size="lg" variant="primary" className="w-full mt-2" disabled={pending} type="submit">
{pending ? 'Signing in...' : 'Sign in'}
</Button>
<div className="text-center text-text">
Don't have an account? <a href="/sign-up" className="text-primary font-medium">Sign up</a>
</div>
</form>
</div>
</main>
)
}


**Key points:**
#
  • signInWithPassword is the Supabase method for credential-based login (as opposed to signUp).
  • useActionState handles the pending state and any returned errors, just like in the sign-up flow. [validate form fields]
  • Make sure your Input components have a name attribute (e.g. name="email", name="password") — formData.get() relies on the name attribute, not id.
  • The redirect('/dashboard') call must happen after a successful sign-in, and since it's inside a Server Action, it works correctly. [setting cookies]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

This is the error I get ```

Error Type

Runtime TypeError

Error Message

Cannot read properties of undefined (reading 'get')

at signIn (src\lib\auth.js:68:26)

Code Frame

66 | const supabase = await createSupabaseServer()
67 |

68 | const email = formData.get('email')
| ^
69 | const password = formData.get('password')
70 |
71 | console.log('[signIn] Form data received:', { email, password: pass...

Next.js version: 16.2.2 (Turbopack)

#

@glossy rapids

This is the error I get ```

Error Type

Runtime TypeError

Error Message

Cannot read properties of undefined (reading 'get')

at signIn (src\lib\auth.js:68:26)

Code Frame

66 | const supabase = await createSupabaseServer()
67 |

68 | const email = formData.get('email')
| ^
69 | const password = formData.get('password')
70 |
71 | console.log('[signIn] Form data received:', { email, password: pass...

Next.js version: 16.2.2 (Turbopack)

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Sorry @heavy jackal, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:

Perhaps try again in 15mins?

heavy jackal
#

Work

#

@glossy rapids

This is the error I get ```

Error Type

Runtime TypeError

Error Message

Cannot read properties of undefined (reading 'get')

at signIn (src\lib\auth.js:68:26)

Code Frame

66 | const supabase = await createSupabaseServer()
67 |

68 | const email = formData.get('email')
| ^
69 | const password = formData.get('password')
70 |
71 | console.log('[signIn] Form data received:', { email, password: pass...

Next.js version: 16.2.2 (Turbopack)

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

The error Cannot read properties of undefined (reading 'get') on formData means that formData is undefined when signIn is called. This is because useActionState changes the function signature — it passes prevState as the first argument and formData as the second argument.

Your current signIn function only accepts formData as the first argument, but useActionState is passing prevState there instead.

Fix the signIn Server Action to accept prevState as the first parameter: [validation errors]

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'
import { redirect } from 'next/navigation'

export async function signIn(prevState, formData) { // <-- add prevState as first param
  console.log('[signIn] Starting sign in process...')

  const supabase = await createSupabaseServer()

  const email = formData.get('email')
  const password = formData.get('password')

  console.log('[signIn] Form data received:', { email, password: password ? '***' : 'MISSING' })

  if (!email || !password) {
    console.error('[signIn] Missing required fields')
    return { error: 'Email and password are required.' }
  }

  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  })

  if (error) {
    console.error('[signIn] Auth error:', error.message)
    return { error: error.message }
  }

  console.log('[signIn] Sign in successful for user:', data.user.id)
  redirect('/dashboard')
}
#

This is the same pattern used with useActionState — when a Server Action is passed to it, the hook injects prevState as the first argument automatically. [useActionState docs]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids RIght now I have this code:

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'
import { redirect } from 'next/navigation'

export async function signUp(formData) {
const supabase = await createSupabaseServer()

const name = formData.get('name')
const email = formData.get('email')
const password = formData.get('password')

if (!name || !email || !password) {
console.error('[signUp] Missing required fields')
return { error: 'Name, email, and password are required.' }
}

const { data, error } = await supabase.auth.signUp({
email,
password,
})

if (error) {
console.error('[signUp] Auth error:', error.message)
return { error: error.message }
}

if (!data?.user) {
console.error('[signUp] No user returned from auth.signUp')
return { error: 'No user was created. The email may already be registered.' }
}

const { data: insertData, error: insertError } = await supabase
.from('owners')
.insert({
auth_id: data.user.id,
name,
email,
})
.select()

if (insertError) {
console.error('[signUp] Insert error:', insertError.message)
return { error: insertError.message }
}
}

export async function signIn(prevState, formData) {
const supabase = await createSupabaseServer()

const email = formData.get('email')
const password = formData.get('password')

if (!email || !password) {
console.error('[signIn] Missing required fields')
return { error: 'Email and password are required.' }
}

const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})

if (error) {
console.error('[signIn] Auth error:', error.message)
return { error: error.message }
}

redirect('/create-user')
}

I want to redirect to /login if it works, and also if I get erros like:
[signUp] Auth error: User already registered, it should show in red text somewere, Right now it shows nothing.

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

You’re very close. Two things to fix:

  1. Redirect to /login after successful sign‑up
  2. Surface error from the Server Action in the form using useActionState

1. Redirect after successful sign‑up

In a Server Action you can call redirect('/login') after all work succeeds. Note that redirect throws and stops execution, so call it at the end, outside any try block. [redirect guide; redirect behavior]

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'
import { redirect } from 'next/navigation'

export async function signUp(formData) {
  const supabase = await createSupabaseServer()

  const name = formData.get('name')
  const email = formData.get('email')
  const password = formData.get('password')

  if (!name || !email || !password) {
    return { error: 'Name, email, and password are required.' }
  }

  const { data, error } = await supabase.auth.signUp({ email, password })

  if (error) {
    // e.g. "User already registered"
    return { error: error.message }
  }

  if (!data?.user) {
    return { error: 'No user was created. The email may already be registered.' }
  }

  const { error: insertError } = await supabase
    .from('owners')
    .insert({
      auth_id: data.user.id,
      name,
      email,
    })

  if (insertError) {
    return { error: insertError.message }
  }

  // Success: redirect to login
  redirect('/login')
}

Same idea for signIn (you already redirect to /create-user):

export async function signIn(prevState, formData) {
  const supabase = await createSupabaseServer()

  const email = formData.get('email')
  const password = formData.get('password')
#

if (!email || !password) {
return { error: 'Email and password are required.' }
}

const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})

if (error) {
return { error: error.message }
}

redirect('/create-user')
}


---

### 2. Show errors (e.g. “User already registered”) in red text

To display validation / auth errors from a Server Action, turn the component that defines the `<form>` into a Client Component and use `useActionState`. The Server Action should return an object (e.g. `{ error: string }`) and you read it from `state` in the component. [[forms validation errors](<https://nextjs.org/docs/app/guides/forms#validation-errors>); [auth form validation](<https://nextjs.org/docs/app/guides/authentication#2-validate-form-fields-on-the-server>)]

**Example sign‑up page:**

```tsx
// app/(login)/sign-up/page.tsx
'use client'

import { useActionState } from 'react'
import { signUp } from '@/lib/auth'
import Input from '@/components/input'
import Button from '@/components/button'
import Image from 'next/image'

const initialState = { error: '' as string | undefined }

export default function SignUpPage() {
  const [state, formAction, pending] = useActionState(signUp, initialState)

  return (
    <main className="flex-1">
      <div className="card bg-white shadow-xl p-4 rounded-lg">
        <div className="flex flex-col items-center justify-center mb-4">
          <Image
            src="/logo.webp"
            alt="PawMatch Logo"
            width={96}
            height={96}
            priority
          />
          <h5 className="text-h5 font-display text-center mt-4">
            Create your account
          </h5>
        </div>
#

<form action={formAction} className="space-y-2">
<Input
id="name"
name="name"
label="Name"
type="text"
placeholder="Your name"
/>
<Input
id="email"
name="email"
label="Email Address"
type="email"
placeholder="example@email.com"
/>
<Input
id="password"
name="password"
label="Password"
type="password"
placeholder="*********"
/>

      {state?.error && (
        <p className="text-sm text-red-500 mt-1">{state.error}</p>
      )}

      <Button
        size="lg"
        variant="primary"
        className="w-full mt-2"
        type="submit"
        disabled={pending}
      >
        {pending ? 'Signing up...' : 'Sign up'}
      </Button>

      <div className="text-center text-text">
        Already have an account?{' '}
        <a href="/login" className="text-primary font-medium">
          Log in
        </a>
      </div>
    </form>
  </div>
</main>

)
}


Key points from the docs applied here:

- The form component is a Client Component (`'use client'`) and uses `useActionState`. [[forms validation errors](<https://nextjs.org/docs/app/guides/forms#validation-errors>)]
- The Server Action (`signUp`) returns an object with an `error` field for expected errors instead of throwing. [[error handling expected](<https://nextjs.org/docs/app/getting-started/error-handling#handling-expected-errors>)]
- The error is rendered conditionally from `state.error` in red text.
#

Do the same pattern for your login page, using useActionState(signIn, initialState) and rendering state.error under the password field.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids
export default function Page() {
return (
<main className="flex-1">
<div className="card bg-white shadow-xl p-4 rounded-lg">
<div className="flex flex-col items-center justify-center mb-4">
<Image
src="/logo.webp"
alt="PawMatch Logo"
width={96}
height={96}
priority
/>
<h5 className="text-h5 font-display text-center mt-4">
Join Paw Match
</h5>
<p className="text-center opacity-50 mt-2">
Create an account to start swiping
</p>
</div>
<form action={signUp} className="space-y-2">
<Input
id="name"
label="Full Name"
placeholder="Enter your full name"
/>
<Input
id="email"
label="Email Address"
type="email"
placeholder="example@email.com"
/>
<Input
id="password"
label="Password"
type="password"
placeholder="*********"
/>
<Button
type="submit"
size="lg"
variant="primary"
className="w-full mt-2"
>
Sign up
</Button>
<div className="text-center text-text">
Already have an account? <a href="/login" className="text-primary font-medium">Log in</a>
</div>
</form>
</div>
</main>
)
}

This is my page, how do I add the error states so it shows correctly?

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

To show error states from your signUp Server Action, you need to:

  1. Make this page a Client Component.
  2. Use useActionState so the action can return { error: string }.
  3. Ensure your inputs have name attributes (FormData uses name, not id).
  4. Render state.error in the form. [Validation errors; Auth form validation]

1. Update your signUp action (server)

You already have something like this; just be sure it returns an object with error on failure and redirects on success:

// src/lib/auth.ts
'use server'

import { createSupabaseServer } from './supabaseServer'
import { redirect } from 'next/navigation'

export async function signUp(formData: FormData) {
  const supabase = await createSupabaseServer()

  const name = formData.get('name')
  const email = formData.get('email')
  const password = formData.get('password')

  if (!name || !email || !password) {
    return { error: 'Name, email, and password are required.' }
  }

  const { data, error } = await supabase.auth.signUp({ email, password })

  if (error) {
    return { error: error.message }
  }

  if (!data?.user) {
    return { error: 'No user was created. The email may already be registered.' }
  }

  const { error: insertError } = await supabase
    .from('owners')
    .insert({
      auth_id: data.user.id,
      name,
      email,
    })

  if (insertError) {
    return { error: insertError.message }
  }

  // success → redirect
  redirect('/login')
}

This matches the pattern of returning structured errors for expected failures. [Expected errors]

#

2. Turn your page into a Client Component and use useActionState

// app/(login)/sign-up/page.tsx
'use client'

import Image from 'next/image'
import { useActionState } from 'react'
import { signUp } from '@/lib/auth'
import Input from '@/components/input'
import Button from '@/components/button'

const initialState = { error: '' as string | undefined }

export default function Page() {
  const [state, formAction, pending] = useActionState(signUp, initialState)

  return (
    <main className="flex-1">
      <div className="card bg-white shadow-xl p-4 rounded-lg">
        <div className="flex flex-col items-center justify-center mb-4">
          <Image
            src="/logo.webp"
            alt="PawMatch Logo"
            width={96}
            height={96}
            priority
          />
          <h5 className="text-h5 font-display text-center mt-4">
            Join Paw Match
          </h5>
          <p className="text-center opacity-50 mt-2">
            Create an account to start swiping
          </p>
        </div>

        <form action={formAction} className="space-y-2">
          <Input
            id="name"
            name="name"              // important
            label="Full Name"
            placeholder="Enter your full name"
          />
          <Input
            id="email"
            name="email"             // important
            label="Email Address"
            type="email"
            placeholder="example@email.com"
          />
          <Input
            id="password"
            name="password"          // important
            label="Password"
            type="password"
            placeholder="*********"
          />

          {state?.error && (
            <p className="text-sm text-red-500 mt-1">{state.error}</p>
          )}
#

<Button
type="submit"
size="lg"
variant="primary"
className="w-full mt-2"
disabled={pending}
>
{pending ? 'Signing up...' : 'Sign up'}
</Button>

      <div className="text-center text-text">
        Already have an account?{' '}
        <a href="/login" className="text-primary font-medium">
          Log in
        </a>
      </div>
    </form>
  </div>
</main>

)
}


Why this works:

- `useActionState` wires your `signUp` Server Action to the form and gives you `state` and `pending`. [[Pending states](<https://nextjs.org/docs/app/guides/forms#pending-states>)]
- The Server Action’s return value (e.g. `{ error: 'User already registered' }`) becomes `state` in the component, so you can render `state.error` in red. [[Validation errors](<https://nextjs.org/docs/app/guides/forms#validation-errors>)]
- On success, `redirect('/login')` runs on the server and navigates away, so the user never sees a stale success state.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids I use jsx, please rewrite

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Here’s the same setup rewritten in plain JS/JSX.

1. Server Action (src/lib/auth.js)

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'
import { redirect } from 'next/navigation'

export async function signUp(formData) {
  const supabase = await createSupabaseServer()

  const name = formData.get('name')
  const email = formData.get('email')
  const password = formData.get('password')

  if (!name || !email || !password) {
    return { error: 'Name, email, and password are required.' }
  }

  const { data, error } = await supabase.auth.signUp({ email, password })

  if (error) {
    // e.g. "User already registered"
    return { error: error.message }
  }

  if (!data?.user) {
    return {
      error: 'No user was created. The email may already be registered.',
    }
  }

  const { error: insertError } = await supabase
    .from('owners')
    .insert({
      auth_id: data.user.id,
      name,
      email,
    })

  if (insertError) {
    return { error: insertError.message }
  }

  // On success, redirect to login
  redirect('/login')
}

This follows the pattern of returning structured errors from a Server Action instead of throwing, so they can be shown in the UI. [Expected errors]

2. Sign‑up page as JSX Client Component

// app/(login)/sign-up/page.jsx
'use client'

import Image from 'next/image'
import { useActionState } from 'react'
import { signUp } from '@/lib/auth'
import Input from '@/components/input'
import Button from '@/components/button'

const initialState = { error: '' }

export default function Page() {
  const [state, formAction, pending] = useActionState(signUp, initialState)
#

return (
<main className="flex-1">
<div className="card bg-white shadow-xl p-4 rounded-lg">
<div className="flex flex-col items-center justify-center mb-4">
<Image
src="/logo.webp"
alt="PawMatch Logo"
width={96}
height={96}
priority
/>
<h5 className="text-h5 font-display text-center mt-4">
Join Paw Match
</h5>
<p className="text-center opacity-50 mt-2">
Create an account to start swiping
</p>
</div>

    <form action={formAction} className="space-y-2">
      <Input
        id="name"
        name="name" // important for FormData
        label="Full Name"
        placeholder="Enter your full name"
      />
      <Input
        id="email"
        name="email"
        label="Email Address"
        type="email"
        placeholder="example@email.com"
      />
      <Input
        id="password"
        name="password"
        label="Password"
        type="password"
        placeholder="*********"
      />

      {state?.error && (
        <p className="text-sm text-red-500 mt-1">{state.error}</p>
      )}

      <Button
        type="submit"
        size="lg"
        variant="primary"
        className="w-full mt-2"
        disabled={pending}
      >
        {pending ? 'Signing up...' : 'Sign up'}
      </Button>

      <div className="text-center text-text">
        Already have an account?{' '}
        <a href="/login" className="text-primary font-medium">
          Log in
        </a>
      </div>
    </form>
  </div>
</main>

)
}


This matches the documented pattern:
#
  • The form component is a Client Component using 'use client'.
  • It uses useActionState with a Server Action that returns an error object. [Validation errors]
  • The error is rendered from state.error and the button uses pending for a loading state. [Pending states]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.

heavy jackal
#

@glossy rapids

POST /sign-up 500 in 891ms (next.js: 11ms, application-code: 880ms)
└─ ƒ signUp({"error":""}) in 17ms src/lib/auth.js
[browser] Uncaught TypeError: formData.get is not a function
at signUp (src\lib\auth.js:10:25)
8 | const supabase = await createSupabaseServer()
9 |

10 | const name = formData.get('name')
| ^
11 | const email = formData.get('email')
12 | const password = formData.get('password')
13 |

I get this again

glossy rapidsBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

formData.get is not a function here means your signUp Server Action is being called with state as the first argument, not FormData.

That happens because you’re now using useActionState(signUp, initialState). With useActionState, the Server Action signature must be:

async function action(prevState, formData) { ... }

Right now your signUp is:

export async function signUp(formData) {
  // ...
  const name = formData.get('name') // ← here formData is actually { error: '' }
}

So formData is the previous state object ({ error: '' }), which has no .get.

Update signUp to accept prevState first and formData second:

// src/lib/auth.js
'use server'

import { createSupabaseServer } from './supabaseServer'
import { redirect } from 'next/navigation'

export async function signUp(prevState, formData) {
  const supabase = await createSupabaseServer()

  const name = formData.get('name')
  const email = formData.get('email')
  const password = formData.get('password')

  if (!name || !email || !password) {
    return { error: 'Name, email, and password are required.' }
  }

  const { data, error } = await supabase.auth.signUp({ email, password })

  if (error) {
    return { error: error.message }
  }

  if (!data?.user) {
    return {
      error: 'No user was created. The email may already be registered.',
    }
  }

  const { error: insertError } = await supabase
    .from('owners')
    .insert({
      auth_id: data.user.id,
      name,
      email,
    })

  if (insertError) {
    return { error: insertError.message }
  }

  redirect('/login')
}
#

This matches the documented pattern for using useActionState with Server Actions, where the action receives prevState as the first parameter and formData as the second. [Validation errors]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@heavy jackal Kapa.ai is still learning and improving, please let me know how I did by reacting below.