Next.jsでAPIから受け取った動画のダウンロードURLを作る

# はじめに
備忘録として残しています。
より良い実装方法があれば、指摘していただけると幸いです。

# コード

```page.tsx
"use client";

import { useState, useCallback } from "react";
import composeFiles from "@/utils/composeFiles";
(styled-componentで作成したものは省略)

export default function Home() {
  const [image, setImage] = useState<File | null>(null);
  const [video, setMovie] = useState<File | null>(null);
  const [modalIsOpen, setModalIsOpen] = useState(false);
  const [modalMessage, setModalMessage] = useState("");
  // ダウンロードURLとして使用する
  const [videoUrl, setVideoUrl] = useState<string | null>(null);

  const handleChangeImage = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    （中略）
  }, []);

  const handleChangeVideo = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    （中略）
  }, []);

  const handleSubmit = async (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    if (image === null || video === null) {
      openModal("画像または動画がアップロードされていません");

      return;
    }
    const response = await composeFiles(image, video);

    setVideoUrl(response);
  };

  return (
    <main>
      <Explanation />
      <UploadForm
        handleChangeImage={handleChangeImage}
        handleChangeVideo={handleChangeVideo}
        handleSubmit={handleSubmit}
      />
      <PreviewImage file={image} />
      <PreviewVideo file={video} />
      {videoUrl && (
        <div>
          <a href={videoUrl} download="processed_video.mp4">
            ダウンロード
          </a>
          <video controls src={videoUrl} />
        </div>
      )}
    </main>
  );
}
```
aタグをクリックすると、APIから返ってきた動画をダウンロードすることができます。

```composeFile.tsx
// page.tsxから呼び出される
// /api/routes.tsにリクエストを送る
"use client";

import axios from "axios";

const composeFiles = async (image: File, video: File) => {
  const formData = new FormData();
  formData.append("image", image);
  formData.append("video", video);
  const response = await axios.post(`/api/compose`, formData, {
    headers: {
      "Content-Type": "multipart/form-data",
    },
    responseType: "blob",
  });

  const blob = response.data;
  const url = URL.createObjectURL(blob);

  return url;
};

export default composeFiles;
```

response.dataをblobとして受け取り、URL.createObjectURL(blob)でダウンロードするのに使用する一時的なURLを作成しています。

```routes.ts
import { NextRequest, NextResponse } from "next/server";
import axios from "axios";

export async function POST(req: NextRequest) {
  try {
    const formData = await req.formData();
    const response = await axios.post(`http://localhost:8080/compose`, formData, {
      headers: { "Content-Type": "multipart/form-data" },
      responseType: "stream",
    });

    return new NextResponse(response.data, {
      status: 200,
      headers: { "Content-Type": "video/mp4" },
    });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  } catch (error: any) {
    console.error("Error uploading files:", error);

    return NextResponse.json({ message: "Error", error }, { status: 500 });
  }
}
```
特に変わったことはしていないと思います。
リクエストデータからformDataを受け取り、APIに渡しています。

リクエスト先のAPIでは受け取った画像と動画を合成しています。
[こちらの記事](https://qiita.com/SBS_Takumi/items/a0cfc6df3b56f2ace36d)に載せているものをFastAPIに移植しました。

# まとめ

APIから返ってきた動画を`blob`として受け取って、`URL.createObjectURL`で一時的なURLを作成しています。
そのURLをaタグのhrefに指定したうえで、download="{任意のファイル名}.mp4"を指定することでダウンロードリンクを作成することができます。
```typescript
<a href={videoUrl} download="video_filename.mp4">ダウンロードリンク<a/>
```

受け取ったURLから動画を表示させたいのですが、下記のコードでは表示できないです。
良い方法をご存知であれば教えていただけると幸いです。
```typescript
<video controls src={videoUrl} />
```