Add video chat to a Next.js app in 30 minutes with Daily Prebuilt
Tags: nextjs, react, video, javascript
Published: 2021-10-07 19:52:08

With the launch of our new [docs site](https://docs.daily.co/?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt), we’ve been spending a lot of time in [Next.js](https://nextjs.org/). We even got a little meta and embedded a [Daily Prebuilt](https://www.daily.co/prebuilt?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) demo, built on Next, into the docs site, also built on Next. 

![Three participants on a video call embedded within another web page prompting for video call test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ayr9oz0lz08vbe5tg16l.jpeg)

The demo lets readers quickly test out Daily calls, and get a sense of what Daily Prebuilt would look like embedded in their own app, right on the docs site. Our docs use Next [API routes](https://nextjs.org/docs/api-routes/introduction) to create temporary Daily rooms dynamically server-side. 

Since our docs codebase isn’t currently public, this post uses our [`/examples/prebuilt/basic-embed` repository](https://github.com/daily-demos/examples/tree/main/prebuilt/basic-embed?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) as a template to show how you can do the same in any Next app. We’ll cover: 

- Setting up the repository locally 
- Using [Next API routes](https://nextjs.org/docs/api-routes/introduction) to create Daily rooms dynamically server-side 
- Creating a Daily callframe and joining a call once we have a room

You’ll need a [Daily account](https://dashboard.daily.co/signup?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) if you don’t have one already. 

Skip to [API routes](#use-api-routes-to-create-daily-rooms-dynamically-server-side) if you already have a Next project that you want to add video chat to, or if you’d rather run [`create-next-app`](https://nextjs.org/docs/api-reference/create-next-app) to start a new app from scratch. 

## Set up the demo repository 

Clone the [`/examples` repo](https://github.com/daily-demos/examples?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt), and `cd examples/prebuilt/basic-embed`. 

Create an `.env` based on `.env.example`, adding your [Daily domain](https://docs.daily.co/reference/rest-api/your-domain?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) (you set this up when you created an account) and API key (you can find this in the "Developers" tab in the [Daily dashboard](https://dashboard.daily.co/developers?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt)): 

```bash
DAILY_DOMAIN="your-domain"
DAILY_API_KEY="Daily API Key"
```
Once you’ve added your own values, run the following from inside `/basic-embed`  to install dependencies and start the server:

```bash
yarn 
yarn workspace @prebuilt/basic-embed dev
```

You should now be able to click "Create room and start" and jump into a Daily Prebuilt call: 

![Clicking on create room and start on a web page starts a call with one video participant](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ij2u3cztfamrbb206e3.gif)

Let’s look at how that all works. 

## Use API routes to create Daily video rooms dynamically server-side 

Our [`/pages` directory](https://github.com/daily-demos/examples/tree/main/prebuilt/basic-embed/pages?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) is where most of the fun happens. [Next pages](https://nextjs.org/docs/basic-features/pages) are React components. They’re associated with routes based on their file names, and come with some other neat built-in features. 

For example, files inside `pages/api` are treated like API endpoints. These are [Next API routes](https://nextjs.org/docs/api-routes/introduction). In development, they are served by your dev servers, but in prod in our demo app they’ll get converted into Vercel functions, technically making them [serverless](https://nextjs.org/docs/deployment#optimized-for-nextjs). 

![Football coach from Ted Lasso Coach Beard raises hands over head excitedly like mind is blown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69jp5z82wdwga5m7r87b.gif)

In our app, we use a Next API route to create Daily rooms:

```javascript
// pages/api/room/index.js 
 
export default async function handler(req, res) {
 if (req.method === 'POST') {
   const options = {
     method: 'POST',
     headers: {
       Accept: 'application/json',
       'Content-Type': 'application/json',
       Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
     },
     body: JSON.stringify({
       properties: {
         enable_prejoin_ui: true,
         enable_network_ui: true,
         enable_screenshare: true,
         enable_chat: true,
         exp: Math.round(Date.now() / 1000) + 300,
         eject_at_room_exp: true,
       },
     }),
   };
 
   const dailyRes = await fetch(
     `${process.env.DAILY_REST_DOMAIN}/rooms`,
     options
   );
 
   const response = await dailyRes.json();
 
   if (response.error) {
     return res.status(500).json(response.error);
   }
 
   return res.status(200).json(response);
 }
 
 return res.status(500);
}
```

All requests to `/room` are handled here, and we’re specifically adding a case to handle a POST request. The request references both the Daily API key and base REST domain in the .env.

We send this request in the `<CreateRoomButton />` component. This component is a button that onClick creates a room: 

```javascript
// components/CreateRoom.js
 
return (
     <Button onClick={createRoom} disabled={isValidRoom}>
       Create room and start
     </Button>
 );
```

`createRoom()` sends a request to the Next `/api/room` endpoint, which makes the Daily endpoint POST request in [`api/room/index`](https://github.com/daily-demos/examples/blob/main/prebuilt/basic-embed/pages/index.js?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) described above: 

```javascript
// components/CreateRoom.js
 
const createRoom = async () => {
   try {
     const res = await fetch('/api/room', {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
       },
     });
    // Abridged snippet 
 };
```

When that request resolves, it returns the Daily response object, including a `url` value. `createRoom()` sets the `room` value stored in local state to the response object’s `url`: 

```javascript
// components/CreateRoom.js
 
const resJson = await res.json();
setRoom(resJson.url);
```

Now that we have a room, we’re ready for a callframe. 

## Create a Daily callframe and join a call 

Our `<Call />` component not only renders `<CreateRoom />`, but also initializes the callframe with a `useEffect` hook: 

```javascript
// components/Call.js
 
useEffect(() => {
   if (callFrame) return;
 
   createAndJoinCall();
 }, [callFrame, createAndJoinCall]);
```

The hook calls `createAndJoinCall()`, a function that: 

- Creates a new Daily callframe, embedding it in the ref we identified `<div ref={callRef} className="call" />` and passing along some [properties](https://docs.daily.co/reference/daily-js/daily-iframe-class/properties#properties?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) we stored in the `CALL_OPTIONS` constant 
- Joins the Daily room using the `room` value stored in local state 
 - Listens for the [`'left-meeting'`](https://docs.daily.co/reference/daily-js/events/meeting-events#left-meeting?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt) event so it can reset app state when the local participant leaves the call 

```javascript
// components/Call.js 
 
const createAndJoinCall = useCallback(() => {
   const newCallFrame = DailyIframe.createFrame(
     callRef?.current,
     CALL_OPTIONS
   );
 
   setCallFrame(newCallFrame);
 
   newCallFrame.join({ url: room });
 
   const leaveCall = () => {
     setRoom(null);
     setCallFrame(null);
     callFrame.destroy();
   };
 
   newCallFrame.on('left-meeting', leaveCall);
 }, [room, setCallFrame]);
```

`createAndJoinCall()` is invoked whether a room is created dynamically in real-time, as we walked through in the `<CreateRoom />` component, or a room is submitted through the input rendered in `<Home />`: 

```javascript
// components/Home.js 
 
<Field label="Or enter room to join">
    <TextInput
        ref={roomRef}
        type="text"
        placeholder="Enter room URL..."
        pattern="^(https:\/\/)?[\w.-]+(\.(daily\.(co)))+[\/\/]+[\w.-]+$"
        onChange={checkValidity}
     />
</Field>
```

The input calls `checkValidity()` as its values change. This function makes sure that the entered text is a valid Daily room URL based on the `pattern` value, and sets the local state value `isValidRoom` to `true` if so: 

```javascript
// components/Home.js 
 
const checkValidity = useCallback(
   (e) => {
     if (e?.target?.checkValidity()) {
       setIsValidRoom(true);
     }
   },
   [isValidRoom]
 );
```

This enables the "Join room" button: 

```javascript
// components/Home.js 
 
<Button onClick={joinCall} disabled={!isValidRoom}>
     Join room
</Button>
```

Clicking the button calls  `joinCall()`, which sets the `room` value stored in local state to the input: 

```javascript
// components/Home.js
 
const joinCall = useCallback(() => {
   const roomUrl = roomRef?.current?.value;
   setRoom(roomUrl);
 }, [roomRef]);
```

The `room` value in local state triggers the callframe creation in `<Call />` in the same way it did when we created a room dynamically. In both cases a `room` value also instructs `index.js` to display the `<Call />` instead of the `<Home />` component, according to this ternary statement:  

```javascript
// pages/index.js      
 
<main>
       {room ? (
         <Call
           room={room}
           expiry={expiry}
           setRoom={setRoom}
           setCallFrame={setCallFrame}
           callFrame={callFrame}
         />
       ) : (
         <Home
           setRoom={setRoom}
           setExpiry={setExpiry}
           isConfigured={isConfigured}
         />
       )}
     </main>
```

## thank u, Next.js 

That’s the core of the app! There are a few other tangential things in the codebase that we didn’t get into, like the `<ExpiryTimer /`>` component and how we put [`getStaticProps()`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) to work checking for env variables, but we welcome you to explore those things yourself and ping us with questions. Or, if you’d rather build your own video chat interface with Next.js, check out our post using [Next with the Daily call object](https://www.daily.co/blog/build-a-real-time-video-chat-app-with-next-js-and-daily/?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt).

## More resources  
- [Next docs](https://nextjs.org/docs/getting-started)
- [Daily Prebuilt guide](https://docs.daily.co/prebuilt?utm_source=dev&utm_campaign=dev-next-js-and-daily-prebuilt)