개발/React

NEXTJS14 에서 SSR 시점에 메타데이터 동적으로 할당할 때 API 2번 호출 되는 문제

큐토 2024. 7. 13. 04:28
import type { Metadata, ResolvingMetadata } from 'next'
 
type Props = {
  params: { id: string }
  searchParams: { [key: string]: string | string[] | undefined }
}
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // read route params
  const id = params.id
 
  // fetch data
  const product = await fetch(`https://.../${id}`).then((res) => res.json())
 
  // optionally access and extend (rather than replace) parent metadata
  const previousImages = (await parent).openGraph?.images || []
 
  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}
 
export default function Page({ params, searchParams }: Props) {}

서버사이드에서 메타데이터를 동적으로 관리하려면 위 처럼 generateMetadata 함수를 사용하라고 한다.

문제는 동적 메타데이터의 정보가 페이지 렌더링에 필요한 데이터셋과 같은 경우

// src\app\[locale]\variables\[keyword]\page.tsx
import {
    HydrationBoundary,
    QueryClient,
    dehydrate,
} from "@tanstack/react-query";
import React from 'react';
import { getVariables } from '@/api/getVariables';
import VariablesPage from "../components/VariablesPage";
import { Variable } from '@/types/variable';
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';

interface VariablesWithQueryPageProps {
    params: {
        locale: string;
        keyword: string;
    };
}

const fetchDataAndMetadata = async (locale: string, keyword: string) => {
    const t = await getTranslations('variables');

    const queryClient = new QueryClient();

    const variables = await queryClient.fetchQuery<Variable[]>({
        queryKey: ['variables', locale, keyword],
        queryFn: () => getVariables(locale, keyword),
    });

    const dehydratedState = dehydrate(queryClient);

    let description = variables.map((variable, i) => (i === 0 ? '' : ', ') + variable.variableName).join('');

    const metadata: Metadata = {
        title: decodeURIComponent(keyword),
        description: description,
        applicationName: t('title')
    };

    return { variables, dehydratedState, metadata };
};

export async function generateMetadata({ params: { locale, keyword } }: VariablesWithQueryPageProps): Promise<Metadata> {
    const { metadata } = await fetchDataAndMetadata(locale, keyword);
    return metadata;
}

const VariablesWithQueryPage = async ({ params: { locale, keyword } }: VariablesWithQueryPageProps) => {
    const { dehydratedState } = await fetchDataAndMetadata(locale, keyword);

    return (
        <HydrationBoundary state={dehydratedState}>
            <VariablesPage locale={locale} keyword={keyword}/>
        </HydrationBoundary>
    );
};

export default VariablesWithQueryPage;​

 

이런 식으로 응용을 하는데 문제는 SSR 시점에 API가 2번 호출 된다. 당연한 것 같기도 하다.

 

// src\app\[locale]\variables\[keyword]\page.tsx
import {
    HydrationBoundary,
    QueryClient,
    dehydrate,
} from "@tanstack/react-query";
import React from 'react';
import { getVariables } from '@/api/getVariables';
import VariablesPage from "../components/VariablesPage";
import { Variable } from '@/types/variable';
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { cache } from "react";

interface VariablesWithQueryPageProps {
    params: {
        locale: string;
        keyword: string;
    };
}

const fetchDataAndMetadata = cache(async (locale: string, keyword: string) => {
    const t = await getTranslations('variables');

    const queryClient = new QueryClient();

    const variables = await queryClient.fetchQuery<Variable[]>({
        queryKey: ['variables', locale, keyword],
        queryFn: () => getVariables(locale, keyword),
    });

    const dehydratedState = dehydrate(queryClient);

    let description = variables.map((variable, i) => (i === 0 ? '' : ', ') + variable.variableName).join('');

    const metadata: Metadata = {
        title: decodeURIComponent(keyword),
        description: description,
        applicationName: t('title')
    };

    return { variables, dehydratedState, metadata };
});

export async function generateMetadata({ params: { locale, keyword } }: VariablesWithQueryPageProps): Promise<Metadata> {
    const { metadata } = await fetchDataAndMetadata(locale, keyword);
    return metadata;
}

const VariablesWithQueryPage = async ({ params: { locale, keyword } }: VariablesWithQueryPageProps) => {
    const { dehydratedState } = await fetchDataAndMetadata(locale, keyword);

    return (
        <HydrationBoundary state={dehydratedState}>
            <VariablesPage locale={locale} keyword={keyword}/>
        </HydrationBoundary>
    );
};

export default VariablesWithQueryPage;

솔루션은 react cache

 

아 nextjs14 appRouter 너무 피곤하다