Next 13 App Dir
When using trpc-swr with the new Next.js 13 React Server Components, some minor changes are required to get it working. This guide will show you how to configure trpc-swr to work with Next.js 13.
1. Mark swr as an external package
Next.js uses their Rust compiler to compile all dependencies of RSC components. SWR unfortunately is not compatible with this compiler due to its use of useRef. This means that SWR needs to be marked as an external package so that it is not compiled by Next.js.
// next.config.mjs
export default {
...
experimental: {
serverComponentsExternalPackages: ["swr"],
...
},
};2. Wrap your root layout trpc.Provider
Create a new file called Providers.tsx in your app folder. This will serve as the file where all your Context providers wrap.
"use client";
export default function Providers({ children }: React.PropsWithChildren<{}>) {
const [client] = useState(() => trpc.createClient());
return <trpc.Provider client={client}>{children}</trpc.Provider>;
}In your Root layout add wrap the children with Providers
import Providers from "./Providers";
export default function RootLayout({ children }: React.PropsWithChildren<{}>) {
return (
<html>
<head />
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}3. Export createSSG helpers
In your trpc folder, create a new file called trpc-ssg.ts and add the following code:
import { appRouter } from "<router-location>";
import { createProxySSGHelpers } from "@trpc-swr/ssr";
export const createSSG = () => {
return createProxySSGHelpers({
router: appRouter,
ctx: {}, // Insert any context trpc needs here.
});
};3. Use createSSG in your pages
There are two ways to use createSSG in your pages:
- Layout level
- Page level
Page level data
Page level data does not require useSWR at all, thus it is very simple to use, and you can use the createSSG helpers directly.
In your pages, import createSSG and use it:
// app/home/page.tsx
import { createSSG } from "<trpc-ssg-location>";
const getData = () => {
const rsc = createSSG();
return rsc.home.getVersion.fetch();
};
export default async function HomePage() {
const version = await getData();
return <div>{version}</div>;
}Thats it, your server component will use trpc-swr to fetch data from your trpc server.
Global
If you want to use trpc-swr in your layout, you can use the createSSG helpers in your layout and pass them down to your pages.
Any client components using useSWR will be able to use the cache render with fresh data on the server:
In order to do this, we must create a client component which acts as a context provider for trpc-swr. We can do this by exporting our 'own' SWRConfig.
- In
app/SWRConfig.tsxexport this component:
"use client";
import { trpc } from "../utils/trpc"; // trpc client library
const SWRConfig = ({
children,
...rest
}: React.ComponentProps<typeof trpc.SWRConfig>) => {
return <trpc.SWRConfig {...rest}>{children}</trpc.SWRConfig>;
};
export default SWRConfig;NOTE This component can be used multiple times anywhere in your App. It simply wraps the native SWRConfig with the data transformers needed for trpc-swr.
- In your layout, import
createSSGand use it. Pass thedehydratedStateto your customSWRConfig:
// app/home/layout.tsx
import { createSSG } from "<trpc-ssg-location>";
import SWRConfig from "./SWRConfig";
const getData = () => {
const rsc = createSSG();
rsc.home.getVersion.fetch();
return rsc.dehydrate();
};
export default async function Layout({ children }) {
return (
<SWRConfig
value={{
fallback: await getData(),
}}
>
{children}
</SWRConfig>
);
}- In your client components: use
trpc-swrnormally, watch it render with data already available on the server:
"use client";
import { trpc } from "utils/trpc";
const Version = () => {
const { data } = trpc.home.getVersion.useSWR();
console.assert(data !== undefined, "data should be available"); // Will not fire since data is available.
return (
<div>
version:
{data!}
</div>
);
};Using both Page level and global data
Lets say you use page level data in your server component, but you render some client components which should have access to this data.
If you've setup your custom SWRConfig, this is trivial.
// app/dashboard/page.tsx
import { createSSG } from "<trpc-ssg-location>";
import SWRConfig from "../SWRConfig";
const getData = (id: number) => {
const rsc = createSSG();
const user = await rsc.users.byId.fetch({ id });
return {
user,
fallback: await rsc.dehydrate(),
};
};
async function DashboardPage() {
const id = 1; // From your router or similar
const { user, fallback } = await getData(id);
return (
<SWRConfig value={{ fallback }}>
<User user={user} />
<UpdateUserForm id={id} /> {/* <-- is a client component since its a form */}
</SWRConfig>
);
}
export default DashboardPage;Use the prefetched data in your client component:
// app/dashboard/components/UpdateUserForm.tsx
import { trpc } from "utils/trpc";
const UpdateUserForm = ({ id }: { id: number }) => {
const { data } = trpc.users.byId.useSWR({ id });
console.assert(data !== undefined, "data should be available"); // Will not fire since data is available.
return (
<form>
<input defaultValue={data!.name} />
</form>
);
};It is possible to prop-drill here, but you can imagine a deep tree of client components requiring this data.