Multi-Page Frames
Frames.js can be used to create multi-page applications by defining multiple Frames that are linked together.
Creating a Multi-Page Application
Frames are connected by Button
targets, similar to how Next.js Link
components work.
Create your frames app
We create a new directory ./frames
with a frames.ts
file to export our frames application from because it needs to be used from multiple routes.
import { createFrames } from "frames.js/next";
export const frames = createFrames({
basePath: "/frames",
});
Define your initial route
The first frame is always fetched via a GET request and is typically included alongside existing OpenGraph data via the generateMetadata
function in Next.js if you have an existing site.
Define the initial frame
Create a ./frames/route.tsx
file that contains your initial frame. This frame will include buttons to navigate to other frames.
/* eslint-disable react/jsx-key */
import { frames } from "./frames";
import { Button } from "frames.js/next";
export const GET = frames(async () => {
return {
image: <div tw="flex">Welcome</div>,
buttons: [
// With query params
<Button
action="post"
target={{ pathname: "/route1", query: { foo: "bar" } }}
>
Go to route 1
</Button>,
// Without query params
<Button action="post" target="/route2">
Go to route 2
</Button>,
],
};
});
Export the initial frame metadata
In your page.tsx
file, fetch the initial frame's metadata and include it alongside your existing page's metadata.
fetchMetadata
is a helper function that fetches the metadata for a frame from the frames.js handler and formats it for use in the generateMetadata
function.
import { fetchMetadata } from "frames.js/next";
export async function generateMetadata() {
return {
title: "My Page",
// provide a full URL to your /frames endpoint
other: await fetchMetadata(
new URL(
"/frames",
process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000"
)
),
};
}
export default function Page() {
return <span>My existing page</span>;
}
Create the other routes
Create additional frames in the ./frames
directory.
Route 1
Create a directory ./frames/route1/route.tsx
with a POST
handler that returns the frame content.
/* eslint-disable react/jsx-key */
import { frames } from "../frames";
import { Button } from "frames.js/next";
export const POST = frames(async (ctx) => {
const foo = ctx.searchParams.foo;
return {
image: <div tw="flex">Route 1 foo: {foo}</div>, // foo: bar
buttons: [
<Button action="post" target="/route2">
Go to route 2
</Button>,
],
};
});
Route 2
Create a directory ./frames/route2/route.tsx
with a POST
handler that returns the frame content.
/* eslint-disable react/jsx-key */
import { frames } from "../frames";
import { Button } from "frames.js/next";
export const POST = frames(async () => {
return {
image: <div tw="flex">Route 2</div>,
buttons: [
<Button action="post" target="/route1">
Go to route 1
</Button>,
],
};
});
(Optional) Navigate back to the initial frame
If you want to navigate back to the initial frame you need to export a POST
handler for the initial route. You may want to refactor the initial frame handler into a frameHandler
variable that is exported as both GET
and POST
import { frames } from "./frames";
const frameHandler = frames(async () => {
return {
image: <div tw="flex">Welcome</div>
buttons: [
<Button action="post" target="/route1">Go to route 1</Button>,
<Button action="post" target="/route2">Go to route 2</Button>,
],
};
});
export const GET = frameHandler;
export const POST = frameHandler;
You can then navigate back to the initial frame by linking to the initial route.
<Button action="post" target="/">
Go back
</Button>
Notes
The second way to navigate between frames is by defining a Button
with type
, post
, with a target
that points at another Frame.
This can be a Frame on the same domain, or a Frame on another website entirely. In order to link between Frames in the same project, you need to set up a frames.js handler on the POST
route of the path defined in the target.