5

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!

2
  • 1
    I don't use next.js , but isn't the error message telling you that you can't export anything other than an asyn function, but export const dynamic = 'force-dynamic is not an async function...
    – Keith
    Aug 23, 2023 at 1:23
  • You're correct about the error, but changing it to a default export triggers another issue in Next.js 13: plaintext x Server actions must be async functions This arises because server actions in Next.js 13 must be async. The line export const dynamic = 'force-dynamic' is intended to address caching, but conflicts with these requirements. I need to find another way to comply with Next.js 13's rules. Any further insights would be welcomed!
    – Vladimirzb
    Aug 23, 2023 at 2:30

3 Answers 3

11

I had a similar issue in next 14 where I was using an index.ts file to centralize my exports of server actions. The issue was that I needed to remove the 'use server' from the index.ts file itself.

WRONG:

'use server'

/**
 * Simplify and centralize server action exports
 */
export { signIn } from "./signIn";
export { signOut } from "./signOut";
export { createComment } from "./createComment";
export { createPost } from "./createPost";
export { createTopic } from "./createTopic";

CORRECT: Remove use server from top of file.

/**
 * Simplify and centralize server action exports
 */
export { signIn } from "./signIn";
export { signOut } from "./signOut";
export { createComment } from "./createComment";
export { createPost } from "./createPost";
export { createTopic } from "./createTopic";

Example of createComment server action

"use server";

export async function createComment() {}

Properly exporting an async function from it with use server at the top of each individual server action file.

Hope this helps someone.

2
  • 4
    thanks. I think you are follwing sir stephen grider course. Your answer helped me a lot. thank you @codeconnoisseur Jan 3 at 21:08
  • it helps indeed !
    – Makoto
    Jan 30 at 11:02
2

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.

0
0

The problem for me was just because in the action file I had another exported object!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.