reactjs - Next.js hvordan bruke tRPC med serverkomponenter på riktig måte


Jeg holder på å lære T3 stack med App-router. Men jeg støtte på et problem med å bruke tRPC. 90 % av tutorialene på internett viser hvordan man kombinerer tRPC med Next.js ved å bruke hooks, for eksempel:

const {data: posts, isLoading} = api.posts.all.useQuery({});

Men det er en hook! Den kan bare brukes i en klientkomponent. Så det ødelegger SSR, SSG, hva som helst. Jeg vet ikke hvorfor alle på YouTube viser denne måten å hente data på. Jeg har sett en video som dette på den offisielle Theo T3 YT-kanalen.

Så, hvordan får man riktig data i serverkomponenter ved å bruke tRPC med next.js?

Jeg fant 2 måter, men jeg har ikke helt funnet dem ut.

  • Måte 1 - ved å bruke createTRPCProxyClient som T3 CLI tilbyr. Jeg lager tRPC serverApi, deretter i en serverkomponent laster jeg data og sender det som props til en CLIENT-komponent som bruker tRPC-hooken useQuery med initialData. Det fungerer, greit nok, men det er så komplisert, ikke sant? I tillegg krever det noStore()-kall før hver serverAPI - det er så forvirrende! Uten noStore() fungerer dev-serveren helt fint, men når jeg kjører npm run build får jeg en feil. P.S. beklager for mye kode

// trpc/server.ts
import "server-only";

import {
  createTRPCProxyClient,
  loggerLink,
  TRPCClientError,
} from "@trpc/client";
import { callProcedure } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { type TRPCErrorResponse } from "@trpc/server/rpc";
import { headers } from "next/headers";
import { cache } from "react";

import { appRouter, type AppRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";
import { transformer } from "./shared";

/**
 * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
 * handling a tRPC call from a React Server Component.
 */
const createContext = cache(() => {
  const heads = new Headers(headers());
  heads.set("x-trpc-source", "rsc");

  return createTRPCContext({
    headers: heads,
  });
});

export const serverApi = createTRPCProxyClient<AppRouter>({
  transformer,
  links: [
    /**
     * Custom RSC link that lets us invoke procedures without using http requests. Since Server
     * Components always run on the server, we can just call the procedure as a function.
     */
    () =>
      ({ op }) =>
        observable((observer) => {
          createContext()
            .then((ctx) => {
              return callProcedure({
                procedures: appRouter._def.procedures,
                path: op.path,
                rawInput: op.input,
                ctx,
                type: op.type,
              });
            })
            .then((data) => {
              observer.next({ result: { data } });
              observer.complete();
            })
            .catch((cause: TRPCErrorResponse) => {
              observer.error(TRPCClientError.from(cause));
            });
        }),
  ],

});

// app/page.tsx
// ...
import { unstable_noStore as noStore } from "next/cache";
import { serverApi } from "@/trpc/server";

const ResentTweetsPage: NextPage = async ({}) => {
    noStore();
    // ^^^^^^^^ How to remove it?

    const tweets = await serverApi.tweet.infiniteFeed.query(
        {},
    );


    return (
        <TabsWrapper
            current="Recent"
        >
            <RecentTweets
                initialTweets={tweets}
            />
        </TabsWrapper>
    );
};

export default ResentTweetsPage

// components/RecentTweets
'use client';

import { api } from "@/trpc/react";
import { serverApi } from "@/trpc/server";

export const RecentTweets: FC<{
    initialTweets: Awaited<ReturnType<(typeof serverApi.tweet.infiniteFeed.query)>>
}> = ({
    initialTweets
}) => {
    const tweets = api.tweet.infiniteFeed.useInfiniteQuery(
        {},
        {
            getNextPageParam: lastPage => lastPage.nextCursor,
            // Thanks for it the page render this data at first loading
            initialData: {
                pageParams: [],
                pages: [
                    initialTweets
                ]
            },
            refetchOnMount: false,
            refetchOnReconnect: false,
            refetchInterval: 1000 * 60
        }
    );

    return (
        <InfiniteTweetList
            tweets={
                tweets.data?.pages.flatMap(page => page.tweets)
            }
            isError={tweets.isError}
            isLoading={tweets.isLoading}
            hasMore={Boolean(tweets.hasNextPage)}
            fetchNewTweets={tweets.fetchNextPage}
        />
    );
};
  • Måte 2 - ved å bruke createCaller. Jeg må fortsatt sende initialTweets til klientkomponenten, og det finnes ingen .query() i await serverClient.tweet.infiniteFeed({}). Jeg ville foretrekke denne metoden, men når jeg kjører npm run build, får jeg denne feilen: Siden kunne ikke gjengis statisk fordi den brukte 'headers' og jeg har ikke funnet ut hvordan jeg fikser det.

// trpc/serverClient.ts
import { appRouter } from "@/server/api/root";
import { httpBatchLink } from "@trpc/client";
import { createTRPCContext } from "@/server/api/trpc";

export const serverClient = appRouter.createCaller(
    await createTRPCContext({} as any)
)

// app/page.tsx
import {NextPage} from 'next';
import React from "react";
import { RecentTweets } from "@/components/RecentTweets";
import { serverClient } from "@/trpc/serverClient";
import { TabsWrapper } from "@/app/feed/components/TabsWrapper";

const ResentTweetsPage: NextPage = async ({}) => {
    const tweets = await serverClient.tweet.infiniteFeed(
        {}
    );


    return (
        <TabsWrapper
            current="Recent"
        >
            <RecentTweets
                initialTweets={tweets}
            />
        </TabsWrapper>
    );
};

export default ResentTweetsPage

Takk for hjelpen

Relaterte artikler