reactjs - Fel: "Endast asynkrona funktioner får exporteras i en 'use server'-fil"


Jag försöker använda layout.tsx i app-katalogen i Next.js 13 för att ha en navigeringslayout som finns på alla sidor. Jag lyckades få det så att när användaren loggar ut/loggar in, ändras navigeringsfältet, men problemet är att jag måste uppdatera sidan (F5) för att se ändringen i navigeringsfältet. Jag tror att detta problem är relaterat till cache, och det är därför jag försöker använda export const dynamic='force-dynamic'.

Jag lade också till klientkomponenten till en serverkomponent eftersom jag trodde att det skulle vara problemet, men det löste inte problemet. Jag ville använda export const dynamic='force-dynamic' för att hantera cachen, men nu stöter jag på ett fel som jag inte verkar kunna lösa. Felmeddelandet jag får är:

Fel

Only async functions are allowed to be exported in a "use server" file.

Och här är den detaljerade felspårningen:

./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();
   `----

Kod

Här är koden för varje relevant fil som orsakar felet:

Fil: 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>
      </>
    )
  }

Fil: 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>
  );
}

Här är koden för RootLayout som jag använder:

// 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>
  );
}

Här är katalogstrukturen för kontext:

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

Fråga

Kan någon förklara varför detta fel uppstår, och hur kan jag fixa det samtidigt som jag behåller layoutuppdateringsfunktionen när jag klickar på logga ut-knappen? Jag vill uppnå detta utan att behöva uppdatera sidan.

Alla insikter eller förslag skulle vara mycket uppskattade! Tack!

Svar #1

Jag hade ett liknande problem i Next 14 där jag använde en index.ts-fil för att centralisera mina export av serveråtgärder. Problemet var att jag behövde ta bort 'use server' från själva index.ts-filen.

FEL :

'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";

KORREKT: Ta bort använd server från toppen av filen.

/**
 * 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";

Exempel på createComment-serveråtgärd

"use server";

export async function createComment() {}

Att korrekt exportera en async-funktion från den med use server högst upp i varje individuell serveraktionsfil.

Hoppas detta hjälper någon.

Svar #2

Taggen "use server" är inte ett alternativ till server-side rendering av komponenter jämfört med "use client". Den bör användas med Server Actions. Om du lägger till "use server" högst upp i en fil, kan du endast exportera async-funktioner från den:

'use server'

export async function addItem(data) { // ✅ This work
 // ...
}
export const foo = "bar"; // ❌ This won't work

En komponent utan "use client" högst upp är en serverkomponent, så ta bort 'use server' från ClientInsideServerLayout.tsx och RootLayout.tsx.

Nu kan det faktum att du måste uppdatera sidan manuellt för att det utloggade tillståndet ska återspeglas hanteras med hjälp av 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);
  }
};

För mer om varför man använder router.refresh(), kolla in den här artikeln av Tim Neutkens från Next.js.

Relaterade artiklar