Skip to content

Next.js

Next.js is a React framework for building full-stack web applications.

Quick Start

You can scaffold a Frog project with Next.js integrated via the create-frog CLI:

npm
npm init frog -- -t next

Manual Installation

Install Next.js

npm install next@latest react@latest react-dom@latest vercel@latest

Build your Frame

Next, scaffold your frame:

app/api/[[...routes]]/route.tsx
/** @jsxImportSource frog/jsx */
import { Button, Frog, TextInput } from 'frog'
 
const app = new Frog({ 
  basePath: '/api',
})
 
app.frame('/', (c) => {
  const { buttonValue, status } = c
  return c.res({
    image: (
      <div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
        {status === 'initial' ? (
          'Select your fruit!'
        ) : (
          `Selected: ${buttonValue}`
        )}
      </div>
    ),
    intents: [
      <Button value="apple">Apple</Button>,
      <Button value="banana">Banana</Button>,
      <Button value="mango">Mango</Button>
    ]
  })
})

Add Handlers

After that, we will add handlers to handle requests in the api/ route:

app/api/[[...routes]]/route.tsx
/** @jsxImportSource frog/jsx */
import { Button, Frog, TextInput } from 'frog'
import { handle } from 'frog/next'
 
const app = new Frog({ 
  basePath: '/api',
})
 
// Uncomment to use Edge Runtime
// export const runtime = 'edge'
 
app.frame('/', (c) => {
  const { buttonValue, status } = c
  return c.res({
    image: (
      <div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
        {status === 'initial' ? (
          'Select your fruit!'
        ) : (
          `Selected: ${buttonValue}`
        )}
      </div>
    ),
    intents: [
      <Button value="apple">Apple</Button>,
      <Button value="banana">Banana</Button>,
      <Button value="mango">Mango</Button>
    ]
  })
})
 
export const GET = handle(app)
export const POST = handle(app)

Setup Devtools

Add Frog Devtools after all frames are defined. This way the devtools can automatically discover all your frames.

app/api/[[...routes]]/route.tsx
import { Button, Frog } from 'frog'
import { handle } from 'frog/next'
import { devtools } from 'frog/dev'
import { serveStatic } from 'frog/serve-static'
 
const app = new Frog({ 
  basePath: '/api',
})
 
app.frame('/', (c) => {
  ...
})
 
devtools(app, { serveStatic })
^ Devtools should be called after all frames are defined.
export const GET = handle(app) export const POST = handle(app)

Add Scripts to package.json

Then we will add a Wrangler scripts to our package.json:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "deploy": "vercel",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "hono": "latest",
    "frog": "latest",
    "next": "latest",
    "react": "latest",
    "react-dom": "latest",
    "vercel": "latest",
  },
}
 

Navigate to Frame

Then, we can navigate to our frame in the browser:

npm run dev

http://localhost:3000/api

Bonus: Deploy

When ready, we can deploy our application.

This example deploys to Vercel via the Vercel CLI (vercel), but you can use any platform that supports Next.js

npm run deploy

Bonus: Add Browser Redirects

If a user navigates to your frame in the browser, we may want to redirect them to the correct Next.js page route that corresponds to the frame.

In the example below, when a user navigates to the /api/foo path of the website via their web browser, they will be redirected to the /foo path (ie. /app/foo/page.tsx in your Next.js App).

Read more on Browser Redirects

app/api/[[...routes]]/route.tsx
/** @jsxImportSource frog/jsx */
import { Button, Frog, TextInput } from 'frog'
import { handle } from 'frog/next'
 
const app = new Frog({ 
  basePath: '/api',
  browserLocation: '/:path',
})
 
// Uncomment to use Edge Runtime
// export const runtime = 'edge'
 
app.frame('/foo', (c) => {
  const { buttonValue, status } = c
  return c.res({
    image: (
      <div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
        {status === 'initial' ? (
          'Select your fruit!'
        ) : (
          `Selected: ${buttonValue}`
        )}
      </div>
    ),
    intents: [
      <Button value="apple">Apple</Button>,
      <Button value="banana">Banana</Button>,
      <Button value="mango">Mango</Button>
    ]
  })
})
 
export const GET = handle(app) 
export const POST = handle(app) 

Bonus: Page & Frame Co-location

If a user shares a link to your website, we may want to render the frame in the page route itself, instead of forcing them to share a link to the Frog API route.

In the example below, when a user shares the / (= /page.tsx in your Next.js App) path of the website on Warpcast, they will be able to see the frame. This leverages the generateMetadata function built-in to Next.js

app/page.tsx
import { getFrameMetadata } from 'frog/next'
import type { Metadata } from 'next'
 
export async function generateMetadata(): Promise<Metadata> {
  const url = process.env.VERCEL_URL || 'http://localhost:3000'
  const frameMetadata = await getFrameMetadata(`${url}/api`)
  return {
    other: frameMetadata,
  }
}