Error: "Importing a component that needs next/headers... That only works in a Server Component."
Unanswered
Brown bear posted this in #help-forum
Brown bearOP
Originally had this working all in one file where as soon as the page reloaded a new entry was added. However, I wanted to make it such that the new entry was added when a button was clicked. I was getting an error due to mixing the client/server functions, so I tried separating into 2 different files, but I'm now getting the error in the screenshot. How would I do this?
notes/page.tsx
utils/supabaseUtils.ts
notes/page.tsx
"use client";
import { createClient } from "@/utils/supabase/server";
import { addMessage } from "@/utils/supabaseUtils";
export default async function Page() {
const supabase = createClient();
const handleClick = async () => {
await addMessage();
};
const { data: messages } = await supabase.from("messages").select();
return (
<div>
{" "}
<button onClick={() => handleClick}> Add message </button>{" "}
<pre>{JSON.stringify(messages, null, 2)}</pre>
</div>
);
}utils/supabaseUtils.ts
"use server";
import { createClient } from "@/utils/supabase/server";
export const addMessage = async () => {
const supabase = createClient();
const { data, error } = await supabase.from("messages").insert([
{
message: "Your new message here",
link: "Your link here",
coordinates: { lat: 37, long: -34 },
},
// Add more objects for additional messages if needed
]);
if (error) {
console.error("Error adding message:", error.message);
} else {
console.log("Message added successfully:", data);
}
return { data, error };
};33 Replies
American Crow
Error seems to be inside
@/utils/supabase/server. What's the content of that?add 'use server' at the top of it 😄
@Jboncz add 'use server' at the top of it 😄
American Crow
yea thats my guess as well
@Jboncz add 'use server' at the top of it 😄
Brown bearOP
to notes/page.tsx? but then i wouldnt be able to use the onclick?
No add it to
@/utils/supabase/serverI just tested, looks like you dont need 'use server' either way show me that file so I can see it.
Brown bearOP
/utils/supabase/server.ts
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
export const createClient = () => {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
try {
cookieStore.set({ name, value, ...options });
} catch (error) {
// The `set` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
remove(name: string, options: CookieOptions) {
try {
cookieStore.set({ name, value: "", ...options });
} catch (error) {
// The `delete` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
);
};when i get use server to that file get this other error
Unhandled Runtime Error
Error: supabase.from is not a function
Source
app/notes/page.tsx (13:45) @ from
11 | };
12 |
> 13 | const { data: messages } = await supabase.from("messages").select();
| ^
14 | return (
15 | <div>
16 | {" "}@Jboncz You cannot have the page be async in a client component
Brown bearOP
mb sorry for late reply
what would be best way to implement this so onclick the messages are added
@Jboncz You cannot have the page be async in a client component
Brown bearOP
could i use useeffect to fix that
I’ll look when I get back to my pc
Brown bearOP
alr
Alright lets prototype this out.
You around?
@Jboncz You around?
Brown bearOP
yea hi
Just about done with an example for you
Brown bearOP
so without hte button i have no errors
alr
I know but I cant see your code in its entirety makes it diffult to troubleshoot, one sec
@Brown bear so without hte button i have no errors
Brown bearOP
but i want it so that on click it adds new one
I gotcha
With this, everytime I click the button to add a post, it calls a server action to add the post, and then changes 'posts' to empty string so it triggers the useEffect again to get updated content
'use client';
import { Button } from "@/components/ui/button";
import { AddPost, getPosts } from "./action";
import { useEffect, useState } from "react";
export default function Page() {
const [posts, setPosts] = useState('');
const [postsLoading, setPostLoading] = useState(true)
const buttonClick = async () => {
const result = await AddPost('abc123\n');
if (result) {
console.log('trigger reload')
setPosts('')
}
console.log(result)
}
useEffect(() => {
const get = async () => {
const results = await getPosts('./myFile.txt')
if (results) {
console.log(results)
setPosts(results)
}
setPostLoading(false)
}
get()
}, [posts])
return (
<>
{postsLoading ?
<>
Loading
</> :
<>
{posts}
</>
}
<Button onClick={() => buttonClick()}>Append to file</Button>
</>
);
}server actions
'use server'
const fs = require('fs').promises;
export async function AddPost(data) {
try {
await fs.appendFile('./myFile.txt', data, 'utf-8');
console.log('The data was appended successfully!');
return true;
}
catch (err) {
console.error(err);
return false;
}
}
export async function getPosts(filePath) {
try {
const data = await fs.readFile(filePath, 'utf-8'); // Read file with utf-8 encoding
console.log(data)
return data;
}
catch (err) {
console.error('Error reading file:', err);
return null; // Or throw the error if preferred
}
}obviously im using a text file in place of your db
🙂
Does that example help?