「Next.js・画像保存」で調べてもなかなかそれっぽい記事が見つからなくて苦労したのでうまくいったものをここに書きます。
環境構築
以下を実行してプロジェクトを作成
npx create-next-app nextjs-image-upload --typescript
以下のサイトを参考にChakra UI
をインストール
(※今回はChakra UI
をフロント部分で使用しているため)
formidable
をインストール
FormDataを変換するのに必要
npm i formidable
npm i @types/formidable
swiper
をインストール
npm install swiper
主にpages
フォルダを触っていきます。
フロント部分・データ送信
Chakra UI
を使用するために_app.tsx
を編集
_app.tsx
import "../styles/globals.css";
import { ChakraProvider } from "@chakra-ui/react";
import type { AppProps } from "next/app";
function MyApp({ Component, pageProps }: AppProps) {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
);
}
export default MyApp;
フォーム画面とデータ送信
swiper
選択画像のプレビュー表示をスライドにしている
onSubmit
関数が送信処理
この中でFormDataを作成してファイルなどを入れてfetchでPOST送信している
index.tsx
import {
Container,
FormLabel,
Heading,
Image,
Input,
} from "@chakra-ui/react";
import type { NextPage } from "next";
import { ChangeEvent, FormEvent, useRef, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react"; //カルーセル用のタグをインポート
import { Pagination, Navigation } from "swiper/modules";
import "swiper/css";
import "swiper/css/navigation"; // スタイルをインポート
import "swiper/css/pagination"; // スタイルをインポート
const Home: NextPage = () => {
const [images, setImages] = useState<Blob[]>([]);
const inputNameRef = useRef<HTMLInputElement>(null);
const inputFileRef = useRef<HTMLInputElement>(null);
// 以下送信処理
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("送信");
const name = inputNameRef.current?.value
const formData = new FormData();
for await(const [i, v] of Object.entries(images)) {
formData.append('files' , v);
}
formData.append("name", name || "");
const post = await fetch(`${window.location.href}api/upload`, {
method: "POST",
body: formData,
});
console.log(await post.json());
};
const handleOnAddImage = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return;
setImages([...e.target.files]);
};
return (
<Container pt="10">
<Heading>Image Form</Heading>
<form onSubmit={onSubmit} encType='multipart/form-data'>
<FormLabel htmlFor="postName">名前</FormLabel>
<Input
type="text"
id="postName"
placeholder="Name"
size="lg"
ref={inputNameRef}
/>
<FormLabel htmlFor="postImages">画像</FormLabel>
<Input
type="file"
id="postImages"
multiple
accept="image/*,.png,.jpg,.jpeg,.gif"
onChange={handleOnAddImage}
ref={inputFileRef}
/>
<Input type="submit" value="送信" margin="10px auto" variant="filled" />
</form>
<Container>
<Swiper
slidesPerView={1} //一度に表示するスライドの数
modules={[Navigation, Pagination]}
pagination={{
clickable: true,
}} // 何枚目のスライドかを示すアイコン、スライドの下の方にある
navigation //スライドを前後させるためのボタン、スライドの左右にある
loop={true}
>
{images.map((image, i) => (
<SwiperSlide key={i}>
<Image
src={URL.createObjectURL(image)}
w="full"
h="40vw"
objectFit="cover"
/>
</SwiperSlide>
))}
</Swiper>
</Container>
</Container>
);
};
export default Home;
サーバー側のAPI・画像を取得と保存
pages/api
フォルダの中にupload.ts
ファイルを作成する
FormDataで送信された内容をformidable
で解析する
日本語の情報が少なかった、あっても古かったり…
/public/images
ディレクトリがないと正常に動かないので作成すること、ここに画像が保存される。
upload.ts
import type { NextApiRequest, NextApiResponse } from "next";
import formidable from "formidable";
import { createWriteStream } from "fs";
export const config = {
api: {
bodyParser: false,
},
};
type Data = {
msg?: string;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
if (req.method !== "POST") return;
const form = formidable({ multiples: true, uploadDir: __dirname });
form.onPart = (part) => {
// let formidable handle only non-file parts
if (part.originalFilename === "" || !part.mimetype) {
// used internally, please do not override!
form._handlePart(part);
} else if (part.originalFilename) {
// 以下でファイルを書き出ししている
console.log(part.name);
// /public/imagesディレクトリがないと正常に動かないので作成すること
const path =
"./public/images/" + new Date().getTime() + part.originalFilename;
const stream = createWriteStream(path);
part.pipe(stream);
part.on("end", () => {
console.log(part.originalFilename + " is uploaded");
stream.close();
});
}
};
// input[type="file"]以外の値はここから見れた
form.on('field', (name, value) => {
console.log(name);
console.log(value);
})
// これを実行しないと変換できない
form.parse(req)
// これでもinput[type="file"]以外の値はここから見れるが、fileは見れない
// form.parse(req, async (err, fields, files) => {
// console.log("fields:", fields); // { name: '*'}
// console.log("files:", files); // {}
// res.status(200).json({ name: "!!!" });
// });
// レスポンス
res.status(200).json({ msg: "success!!" });
}