Issue
I am trying to use layout.tsx
in the app
directory of Next.js 13 to have a nav layout that is present on all the pages. I managed to make it so that when the user logs out/signs in, the navbar changes, but the issue is that I have to refresh the page (F5) to see the change on the navbar. I think this issue is related to the cache, and that's why I am trying to use export const dynamic = 'force-dynamic'
.
I also added the client component to a server component because I thought that would be the issue, but it didn't solve the problem. I wanted to use export const dynamic = 'force-dynamic'
to deal with the cache, but now I'm encountering an error that I can't seem to resolve. The error message I'm getting is:
Error
Only async functions are allowed to be exported in a "use server" file.
And here is the detailed error trace:
./app/components/ClientInsideServerLayout.tsx
Error:
x Only async functions are allowed to be exported in a "use server" file.
,-[C:\Users\zantl\OneDrive\Documentos\GitHub\sssss\gestion-gastos-supabase\app\components\ClientInsideServerLayout.tsx:2:1]
2 |
3 | import { getSessionStatus } from "../ServerActions/isUserLoggedIn";
4 | import ClientLayout from "./ClientLayout"
5 | export const dynamic = 'force-dynamic'
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6 |
7 | export async function ClientInsideServerLayout() {
7 | const isLoggedIn = await getSessionStatus();
`----
Code
Here's the code for each relevant file that's causing the error:
File: ClientInsideServerLayout.tsx
'use server';
import { getSessionStatus } from "../ServerActions/isUserLoggedIn";
import ClientLayout from "./ClientLayout"
export const dynamic = 'force-dynamic'
export async function ClientInsideServerLayout() {
const isLoggedIn = await getSessionStatus();
return (
<>
<ClientLayout isLoggedIn={isLoggedIn}></ClientLayout>
</>
)
}
File: ClientLayout.tsx
'use client'
import Link from 'next/link';
import { useRouter } from 'next/navigation'
export default function ClientLayout({ isLoggedIn }: { isLoggedIn: boolean }) {
const router = useRouter();
const signOut = async () => {
try {
const response = await fetch("http://localhost:3000/auth/sign-out", {
method: "POST"
});
if (response.ok) {
router.push("/")
console.log('The resource has been permanently moved.');
}
} catch (error: unknown) {
console.error('An error occurred:', error);
}
};
const logIn = () =>{
router.push("/login")
}
return (
<nav className="flex items-center justify-between flex-wrap bg-teal-500 p-6">
<div className="flex items-center flex-shrink-0 text-white mr-6">
{/* ... logo code ... */}
</div>
<div className="block lg:hidden">
{/* ... button code ... */}
</div>
<div className="w-full block flex-grow lg:flex lg:items-center lg:w-auto">
<div className="text-sm lg:flex-grow">
<Link href="/ingresos" passHref>
<span className="block mt-4 lg:inline-block lg:mt-0 text-teal-200 hover:text-white">
Ingresos
</span>
</Link>
</div>
<div>
{isLoggedIn ? (
<button onClick={signOut} className="inline-block text-sm px-4 py-2 leading-none border rounded text-white border-white hover:border-transparent hover:text-teal-500 hover:bg-white lg:mt-0">Log Out</button>
) : (
<>
<button onClick={logIn} className="inline-block text-sm px-4 py-2 leading-none border rounded text-white border-white hover:border-transparent hover:text-teal-500 hover:bg-white lg:mt-0 mr-2">Log In</button>
<button className="inline-block text-sm px-4 py-2 leading-none border rounded text-white border-white hover:border-transparent hover:text-teal-500 hover:bg-white lg:mt-0">Sign Up</button>
</>
)}
</div>
</div>
</nav>
);
}
Here's the code for the RootLayout
that I'm using:
// Existing RootLayout code
'use server';
import './globals.css';
import { getSessionStatus } from './ServerActions/isUserLoggedIn';
import { ClientInsideServerLayout } from './components/ClientInsideServerLayout';
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const isLoggedIn = await getSessionStatus();
return (
<html lang="en">
<body>
<div>
<ClientInsideServerLayout></ClientInsideServerLayout>
{children}
</div>
</body>
</html>
);
}
Here's the directory structure for context:
app/
auth/
callback/
route.ts
sign-in/
route.ts
sign-out/
route.ts
sign-up/
route.ts
components/
ClientInsideServerLayout.tsx
ClientLayout.tsx
Login.tsx
tablaIngresos.tsx
ingresos/
page.tsx
login/
messages.tsx
page.tsx
ServerActions/
isUserLoggedIn.ts
_examples/
client-component/
page.tsx
route-handler/
route.ts
server-action/
page.tsx
server-component/
page.tsx
auth-form.tsx
favicon.ico
globals.css
layout.tsx
page.tsx
components/
LogoutButton.tsx
NextJsLogo.tsx
SupabaseLogo.tsx
types/
supabase.ts
.env.local
.gitignore
middleware.ts
next-env.d.ts
next.config.js
package-lock.json
package.json
postcss.config.js
README.md
tailwind.config.js
tsconfig.json
Question
Can someone explain why this error is happening, and how can I fix it while keeping the layout update functionality when clicking the log-out button? I want to achieve this without having to refresh the page.
Any insights or suggestions would be greatly appreciated! Thank you!
Solution
The "use server"
tag is not an alternative for server-side rendering of components to "use client"
. It should be used with Server Actions. If you add "use server"
at the top of a file, you can only export from it async
functions:
'use server'
export async function addItem(data) { // ✅ This work
// ...
}
export const foo = "bar"; // ❌ This won't work
A component with no "use client"
at the top is a server component, so remove the 'use server'
from ClientInsideServerLayout.tsx
and RootLayout.tsx
.
Now, the fact that you have to refresh the page manually to have the logged-out state reflected can be dealt with with the help of router.refresh()
:
const signOut = async () => {
try {
const response = await fetch("http://localhost:3000/auth/sign-out", {
method: "POST",
});
if (response.ok) {
router.push("/");
router.refresh();
}
} catch (error: unknown) {
console.error("An error occurred:", error);
}
};
For more on why using router.refresh()
, checkout this article by Tim Neutkens from Next.js.
Answered By - Youssouf Oumar
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.