-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Support RSC & App Layouts #3297
Comments
tldr; Have We have settled on using the Next13 The main idea is to think of it as using plain node or express on the back end and a separate react app on the front. There are two configs for TRPC, one for the Next API handler and one for the client. This works, but since we are running next, those client components may run on the server on the first-page load, which breaks stuff. The workaround is to only do the TRPC queries inside dynamically imported components with All this works, but it is a bit of a pain, and I feel some tweaks could make this setup easier. And most importantly, I think this is a worthwhile goal. The ultimate expression of what TRPC needs to be in the new RSC paradigm is an important question, but it will take some time to figure out. But in the meantime, if we could make this approach easier, it would give more people an opportunity to try out TRPC in next13 and provide feedback. I don't feel I know the codebase well enough to tackle a PR on my own, but perhaps with some guidance I could get something together. Thoughts? |
Hello, I was wondering if there's been any progress on this issue. With Next.js working towards stabilizing the app directory, more people will be switching over and with that wishing to use tRPC with it. Cheers! |
Cheering on this development! I'm excited to see it soon. |
I stopped using the |
I don't say that everything is fine and perfect, just try to figure out what we have right now and what probably works: Server: Client: Create a TrpcProvider as a client-component: "use client"
import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
import {httpBatchLink} from "@trpc/client"
import {useState} from "react"
import {trpc} from "~/utils/trpc"
export const TrpcProvider: React.FC<{children: React.ReactNode}> = p => {
const [queryClient] = useState(() => new QueryClient())
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: "http://localhost:3000/api/trpc"
})
]
})
)
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{p.children}
</QueryClientProvider>
</trpc.Provider>
)
} You don't have to, but you can even use it at the root level of your layout // app/layout.tsx
import {TrpcProvider} from "~/utils/trpc"
export default function Layout({children}: {children: React.ReactNode}) {
return (
<TrpcProvider>
<html>
<head>{/* snip */}</head>
<body>{children}</body>
</html>
</TrpcProvider>
)
} I've tested it and the content is rendered on the server. Its supposed to work this way. Actually its one of core concepts of the new next 13 RSC implementation. Now this is for using trpc on the client. So if I get it correct, the whole missing point currently is to be able to run it on the RSC as well, right? Or are there some "details" that make the approach above inefficient or bad also on client in some way? |
I think the following needs to be considered - the doc page is already there, even though the link to it is disabled: This will eliminate the need to use |
Route handlers just dropped! @KATT Any word about progress on this? I assume there will be some work to do with this announcement but I'd love to hear from the team on where things are at :) |
@michaelhays I migrated my trpc apis to route handlers, and it was simple, you just have to use the
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { createContext } from './context.ts';
import { appRouter } from './router.ts';
const handler = (request: Request) => {
return fetchRequestHandler({
endpoint: '/api/trpc',
req: request,
router: appRouter,
createContext,
});
}
export const GET = handler;
export const POST = handler; Keep in mind that you would have to change your createContext function signature also. |
@Fredkiss3 Does that work without “use client”? |
@ansh It doesn't, what I did is just move the tRPC api endpoint from next api routes to route handlers. |
How would one get access to the current next-auth session in Trying to do something like this: export async function createContext({ req, resHeaders }: { req: NextApiRequest; resHeaders?: Record<string, string> }) {
const session = await getServerSession(req, resHeaders, authOptions) Fails because I'm using appDir and next-auth and would like my resolvers to have access to the current user. -- Edit: this appears to work but I'm not sure how correct it is export async function createContext({ req }: { req: NextApiRequest }) {
const res = new ServerResponse(req)
const session = await getServerSession(req, res, authOptions)
return {
user: session?.user,
}
} |
@Fredkiss3 how are you then calling/querying that from the front-end components and bringing types through? my invocation of an exported
maybe you're using the "usage with React" rather than "usage with Next" guidance as @akomm suggested above? I'll try that now also edit: yeah everything works when following @akomm 's suggestion, my bad |
@akomm Can you share the whole project structure? I've encountered some similar problems and I'd like to know how to deal with it. |
As I've explained here, don't use the next HOC, use the react approach: Judging by @mysterybear's comment he used HOC. And if you have the same problem, it will be for the same reason. Unless something changed in some recent update. |
@akomm There is a |
It comes from the page I've linked: // utils/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../path/to/router.ts';
export const trpc = createTRPCReact<AppRouter>(); |
I realize this is potentially a lot to ask of someone, but if somebody who has it working already could create a tiny barebones repo, I think it would clear up a lot of back and forth here |
@AlanMorel I have been trying to port an existing Next 13 app, so I ended up assembling a little example repo. Only the client makes RPC calls, though, no RPCs from RSCs. |
Any more movement on this @KATT ?? Seems like |
Planning to work on this thursday and friday. It's hard to design an API when the full story isn't there yet:
|
We might release something prefixed as |
Do you anticipate something wildly different to the approach in https://github.com/trpc/next-13 ? I share your concern about the userland API that is a bit unergonomic ATM (on top of the server/client boundary nesting doll pattern) |
any updates regarding the unstable release? |
In Next.js |
For using trpc on the server side in nextjs 13 app router I used the createTRPCProxyClient approach. This in the case where the trpc endpoints are served from another backend. trpcServerComponent.ts import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "../../trpc-server-in-monorepo/src/trpcRouters";
export const trpcProxyClient = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: TRPC_SERVER_URL,
}),
],
}); And using it for example to generate page metadata: import { trpcProxyClient } from "../../utils/trpcServerComponent";
type Params = {
slug: string;
};
type Props = {
params: Params;
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata({ params, searchParams }: Props, parent: ResolvingMetadata): Promise<Metadata | undefined> {
return trpcProxyClient.endpoint.query({slug:params.slug}).then((response)=>{ return {title: response.title}})
}
export default async function Page(...... This seems to work well, however is this a bad approach with drawbacks? |
DARN, this thing hasn't been released to work with, and I've been stuck on it for so long that I even ended up reading API codes, LOL. I hope support for the app router is released as soon as possible; it would be a fantastic! |
AFAIK there is a way to use it with App Router already. I don't know why TRPC team hasn't updated the doc with these examples, but it's actually working solutions you can adapt. https://youtu.be/qCLV0Iaq9zU?si=-Cf4ZmpsgSDUzeJ7 |
yeah i know this, but it uses createTRPCReact and not createTRPCNext, also it uses adatapter as fetch instead of next, |
Yes, tried it, it doesn't work fine. The session is getting lost |
There is a PR open on the T3 Stack using this approach, seems to be working flawlessly |
Any progress on this, now that server actions are stable? nextjs.org/blog/next-14 |
@ajdiyassin Depends what you are asking for specifically. I am using tRPC with RSC's and App dir, but if you are asking specifically about out-of-the-box server actions, I'd keep an eye on #4567 reopening once they make progress. |
@ajdiyassin We're using the ts-oss repo recommendation from @giomogna and it's working well. I'd suggest starting there. |
T3 app router seems to work well, however, i'm struggling with getting subscription websocket server to work because of createTRPCContext uses next headers. This causes header invariant warnings because of passing the context and router to applyWSSHandler. |
@shammalie agree, t3 experimental app router works fine, but I also can't get subscriptions running. If anyone has an example using the t3-stack as a base and could share it, that would be fantastic :) |
@thacoon, this might not be exactly what you're looking for, but for websockets, I ended up using a custom router on top of a t3 app scaffolded using the experimental app router feature. It's not the most ideal solution (I'm not sure if you can deploy this to vercel), but it works for my use case and depending on yours, it might work too. my server entrypointconst app = next({
dev: env.NODE_ENV !== "production",
hostname: env.HOSTNAME,
port: env.PORT,
customServer: true,
});
await app.prepare();
const getHandler = app.getRequestHandler();
const upgradeHandler = app.getUpgradeHandler();
const server = createServer((req, res) => {
// routes starting with /api/trpc are handled by trpc
if (req.url?.startsWith("/api/trpc")) {
const path = new URL(
req.url.startsWith("/") ? `http://127.0.0.1${req.url}` : req.url,
).pathname.replace("/api/trpc/", "");
return void nodeHTTPRequestHandler({
path,
req,
res,
router: appRouter,
createContext: ({ req }) => {
return createTRPCContext({
req,
res,
});
},
});
}
getHandler(req, res).catch((error) => {
logger.error(error);
res.statusCode = 500;
res.end("Internal Server Error");
});
});
// create the websocket server
const wss = new WebSocketServer({ noServer: true });
const trpcHandler = applyWSSHandler({
wss,
router: appRouter,
createContext: ({ req }) => {
return createTRPCContext({
req,
});
},
});
process.on("SIGTERM", () => {
logger.warn("SIGTERM received, shutting down...");
trpcHandler.broadcastReconnectNotification();
server.close(() => {
process.exit(0);
});
});
// handle the upgrade
server.on("upgrade", (req, socket, head) => {
// send trpc requests to the trpc server
if (req.url?.startsWith("/api/trpc")) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit("connection", ws, req);
});
} else {
void upgradeHandler(req, socket, head);
}
});
server.listen(env.PORT, env.HOSTNAME, () => { ... }); |
@ItzDerock thanks for your quick reply. I also just made it worked (for development) based on https://github.com/trpc/examples-next-prisma-websockets-starter, this example is not using the app router but it helped me solving it. DetailsProject is based on the t3-stack using app router. My src/server/wsServer.ts looks like this now (copied from the example above): import {
applyWSSHandler,
type CreateWSSContextFnOptions,
} from '@trpc/server/adapters/ws';
import { WebSocketServer } from 'ws';
import { appRouter } from '~/server/api/root';
import { type CreateNextContextOptions } from '@trpc/server/adapters/next';
const wss = new WebSocketServer({
port: 3001,
});
export const createContext = async (
opts: CreateNextContextOptions | CreateWSSContextFnOptions
) => {
return {};
};
const handler = applyWSSHandler({
wss,
router: appRouter,
createContext,
});
wss.on('connection', (ws) => {
console.log(`➕➕ Connection (${wss.clients.size})`);
ws.once('close', () => {
console.log(`➖➖ Connection (${wss.clients.size})`);
});
});
console.log('✅ WebSocket Server listening on ws://localhost:3001');
process.on('SIGTERM', () => {
console.log('SIGTERM');
handler.broadcastReconnectNotification();
wss.close();
}); The types for createContext is wrong right know and I need to fix it but it works, after some hours of trial and error. And my 'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
createWSClient,
loggerLink,
unstable_httpBatchStreamLink,
wsLink,
} from '@trpc/client';
import { createTRPCReact } from '@trpc/react-query';
import { useState } from 'react';
import { type AppRouter } from '~/server/api/root';
import { getUrl, transformer } from './shared';
export const api = createTRPCReact<AppRouter>();
export function TRPCReactProvider(props: {
children: React.ReactNode;
cookies: string;
}) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
api.createClient({
transformer,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
wsLink({
client: createWSClient({
url: `ws://localhost:3001`,
}),
}),
unstable_httpBatchStreamLink({
url: getUrl(),
headers() {
return {
cookie: props.cookies,
'x-trpc-source': 'react',
};
},
}),
],
})
);
return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</api.Provider>
</QueryClientProvider>
);
} And similar to the example I have this in my package.json: {
...
"scripts": {
...
"dev:wss": "dotenv tsx watch src/server/wsServer.ts --tsconfig tsconfig.json",
"dev:next": "next dev",
"dev": "run-p dev:*",
...
},
...
} |
@thacoon I've tried to reuse the context in T3 app router, but i get errors with next-auth, since the app router context collects the session via getServerAuthSession(), this proceeds with the following: ProviderX is not a function, ProviderX being Discord in this example, or any provider. I'm realativly new to typescript so it could be a config issue, but i beleive the function error comes from the module augmentation because the compiler can't find next-auth module? The error above comes from running the WSServer. Here's the repo i use to test weird and wacky things with T3 stack. |
@ItzDerock I tried your example below but couldn't get it to work, could you have a look? |
Off topic
In your local .env I think you need to assign DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET some value (at least just something), I think this is the error you get if they are empty. |
This comment was marked as abuse.
This comment was marked as abuse.
There is an experimental release. I will start adopting RSC myself in prod over the next few months and it'll get more love gradually throughout that - some other core members are actively looking at this too. Please do not |
Nothing but love and respect for everything you do here Alex! If anything, I do understand the desire to stay in the know for these things that impact the future of the library so much, just for an understanding of where they are in the pipeline. I've been trying to follow along by checking various branches and PRs here and in T3 every couple of weeks, but haven't been able to figure much out. Even a short message from time-to-time such as "This isn't stable enough in Next.js, we're going to wait at least several months before releasing something" or even "We're working on other things for now" is always much appreciated :) |
@KATT where can we find the experimental release so that we can test it too? :) Thanks for all your hard work! 💯 |
I would be willing to build out a POC for this if it hasn't been done yet. My proposed approach would be to move the react query client from context into the trpc client or a wrapper. This would allow us to provide react query's prefetchQuery functionality inside of server components. We already seem to have this functionality, it's just accessed with useContext, which won't work in a server component. Thoughts? |
For folks who want to use tRPC with app router and seek examples, this the best in-prod codebase out there - Till we get something stable, this is a great stopgap. |
Can confirm documenso solution is a solid one. I have used it to bootstrap a new SaaS, albeit with v11 beta. |
As far as I can tell from the github.com/documenso/documenso.git repo this is just doing client side support. Doesn't (that I could see) have any RSC, SSR elements. FWIW It's pretty similar to the setup I'm currently using, which I did not get directly from that repo but is possibly inspired by. I can confirm that it does work but not for prefetching (or equiv) |
@vertis documenso uses data fetching with async functions in server components, and when they mutate the data using trpc, they just call In server components
In client components
Again, everything is a stopgap. (You can also use this: https://youtu.be/qCLV0Iaq9zU?si=HSqR_FIEWsOz2F-b&t=933) |
There is a lot of info on this thread and I'm kinda lost. But just to get it straight, is the best approach of using trpc with the App Router is to use This seems to be the approach used by the repo examples I've gathered:
Am I getting this right? EDIT: Some nice info here (seems relevant even though it's 2 years old): #3185 (comment) |
@musjj The repo mentioned there has been archived. Here is the latest one with the official trpc roadmap for rsc support. |
Describe the feature you'd like to request
We should make official support for Next 13 app layouts & RSC.
Describe the solution you'd like to see
Being able to transparently use tRPC identically in RSC-components and in
"use client"
-components.This is very open-ended and hard to answer without tonnes of exploration.
We've done some in https://github.com/trpc/next-13 and experiments of new Next.js adapters in https://github.com/trpc/examples-next-app-dir
Rough outline / sketches
Leverage "forked" versions of the tRPC client outlined by @sebmarkbage in this tweet.
Folder structure
With this structure we should be able to streamline the API to be exactly the same within RSC & in
"use client"
components.Usage
Again, it should not matter if the below is done within a RSC-component or in a
"use client"
one.createContext()
In the
createContext
function we'll need to be able to distinguish what type of call it is.Next 13 have some new magical
headers()
andcookies
- we should probably call these or allow for a way of calling them within RSC.Additional information
Open questions
"use client"
that is fetched through SSR & when it's mounted on the client? React Query doesn't seem to support this yet (& I'm guessing it's because lack of support from React)ssrLink
to avoid calls over the network?Update - Prior art:
From SyncLinear.com | T-74
Funding
The text was updated successfully, but these errors were encountered: