Skip to content

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.

frames.ts
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.

route.tsx
/* 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: "/frames/route1", query: { foo: "bar" } }}
      >
        Go to route 1
      </Button>,
      // Without query params
      <Button action="post" target="/frames/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.

page.tsx
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.

route1.tsx
/* 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="/frames/route2">
        Go to route 2
      </Button>,
    ],
  };
});

Route 2

Create a directory ./frames/route2/route.tsx with a POST handler that returns the frame content.

route2.tsx
/* 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="/frames/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

route.tsx
import { frames } from "./frames";
 
const frameHandler = frames(async () => {
  return {
    image: <div tw="flex">Welcome</div>
    buttons: [
      <Button action="post" target="/frames/route1">Go to route 1</Button>,
      <Button action="post" target="/frames/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="/frames">
  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.