Next.js 'Cannot find module './filename.jpeg'。没有足够的时间上传图片

Next.js 'Cannot find module './filename.jpeg'. Not enough time to upload the image

尝试至少半周时间来解决这个问题。我正在尝试创建播放列表页面。

首先,我使用 getServerSideProps 获取有关播放列表的信息,并将其发送给州政府。然后我会改变这个状态,直到用户离开页面。 State 是对象数组,其中对象是有关播放列表的信息。每个对象都通过道具发送到特殊的播放列表组件。

如果用户想要创建新的播放列表,他打开模式 window 并设置信息(名称和描述)并为其选择自己的图片(或者用户可以保留默认图片)。提交后此信息将被发送到数据库并通过sharp.js修复图片,然后通过multer保存为服务器上的文件。然后模态 window 将关闭,用户应该会看到更新后的播放列表。

播放列表组件包含 next/image 组件。在 src 中,我通过 (requrie(.../${avatar}) 获取图像。在第一次渲染或使用默认图片创建新播放列表后,我的构建工作完美。 但是如果用户上传自己的图片,然后(提交后)页面立即中断并出现错误“找不到模块./filename.jpeg”,然后在 2-3 秒内错误消失,用户只有白屏(直到完全重新加载)。

第Playlists.tsx

  const Playlists: React.FC<PlaylistsProps> = ({ user, playlists }) => {
  const [playlistList, setPlaylistList] = React.useState(playlists);
  const [isModalActive, setIsModalActive] = React.useState(false);
  const handleAddPlaylistClick = () => {
    setIsModalActive(true);
  };

  const handleUploadedClick = () => {
    alert("Uploaded playlist");
  };

  return (
    <>
      <div
        className={clsx({
          [styles.mask]: isModalActive,
        })}
      />
      <PlaylistModal
        active={isModalActive}
        modalClose={setIsModalActive}
        setPlaylistList={setPlaylistList}
      />
      <main className={styles.wrapper}>
        <div className={styles.main}>
          <Header name={user.userName!} avatar={user.avatarUrl!} />
          <Aside />
          <div className={styles.title}>
            <div className={styles.picture}>
              <Image
                src="/logo/logo-love-1000.png"
                width={150}
                height={150}
                alt="logo"
              />
            </div>
            <div className={styles.text}>
              <span>Playlists</span>
            </div>
          </div>
          <section className={styles.playlists_wrapper}>
            <ul className={styles.playlists}>
              <li
                onClick={handleAddPlaylistClick}
                className={clsx(styles.playlist, styles.compulsory)}
              >
                <svg
                  viewBox="0 0 512 512"
                  className={clsx(styles.avatar, styles.svg)}
                >
                  <path d="m256 512c-141.164062 0-256-114.835938-256-256s114.835938-256 256-256 256 114.835938 256 256-114.835938 256-256 256zm0-480c-123.519531 0-224 100.480469-224 224s100.480469 224 224 224 224-100.480469 224-224-100.480469-224-224-224zm0 0" />
                  <path d="m368 272h-224c-8.832031 0-16-7.167969-16-16s7.167969-16 16-16h224c8.832031 0 16 7.167969 16 16s-7.167969 16-16 16zm0 0" />
                  <path d="m256 384c-8.832031 0-16-7.167969-16-16v-224c0-8.832031 7.167969-16 16-16s16 7.167969 16 16v224c0 8.832031-7.167969 16-16 16zm0 0" />
                </svg>
                <span className={styles.name}>Add new playlist</span>
              </li>
              <li
                onClick={handleUploadedClick}
                className={clsx(styles.playlist, styles.compulsory)}
              >
                <svg
                  className={clsx(styles.svg, styles.avatar)}
                  version="1.1"
                  viewBox="0 0 490.667 490.667"
                >
                  <path
                    d="M245.333,0C110.059,0,0,110.059,0,245.333s110.059,245.333,245.333,245.333s245.333-110.059,245.333-245.333
S380.608,0,245.333,0z M245.333,469.333c-123.52,0-224-100.48-224-224s100.48-224,224-224s224,100.48,224,224
S368.853,469.333,245.333,469.333z"
                  />

                  <path
                    d="M245.333,106.667c-5.888,0-10.667,4.779-10.667,10.667v256c0,5.888,4.779,10.667,10.667,10.667S256,379.221,256,373.333
v-256C256,111.445,251.221,106.667,245.333,106.667z"
                  />

                  <path
                    d="M338.219,195.115l-85.333-85.333c-4.16-4.16-10.923-4.16-15.083,0l-85.333,85.333c-4.16,4.16-4.16,10.923,0,15.083
c4.16,4.16,10.923,4.16,15.083,0l77.781-77.781l77.781,77.803c2.091,2.069,4.821,3.115,7.552,3.115
c2.731,0,5.461-1.045,7.552-3.136C342.379,206.037,342.379,199.275,338.219,195.115z"
                  />
                </svg>

                <span className={styles.name}>Uploaded songs</span>
              </li>
              {playlistList.map((obj, id: number) => (
                <Playlist key={id} name={obj.name} avatar={obj.avatarUrl} />
              ))}
            </ul>
          </section>
        </div>
      </main>
    </>
  );
};

export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
  try {
    const user = await checkAuth(ctx);

    if (!user) {
      return {
        props: {},
        redirect: {
          permanent: false,
          destination: "/auth/login",
        },
      };
    }
    if (user.genrePreferences?.length == 0) {
      return {
        props: {},
        redirect: {
          permanent: false,
          destination: "/welcome",
        },
      };
    }
    const playlists = await Api(ctx).getPlaylists();

    return {
      props: {
        playlists,
        user,
      },
    };
  } catch (error) {
    console.warn(error);
  }
};

PlaylistModal.tsx(模态 window)

const PlaylistModal: React.FC<PlaylistModalProps> = ({
  active,
  modalClose,
  setPlaylistList,
}) => {
  const [imageUrl, setImageUrl] = React.useState<string>("");
  const [imageFile, setImageFile] = React.useState<File>();
  const [playlistInfo, setPlayListInfo] = React.useState<PlaylistInfo>({
    name: "",
    description: "",
  });

  const sendInfo = async () => {
    try {
      const formData = new FormData();
      if (imageFile) {
        formData.append("avatar", imageFile);
      } else {
        formData.append("avatar", "");
      }
      formData.append("name", playlistInfo.name);
      formData.append("description", playlistInfo.description);
      const result = await Api().createPlaylist(formData);
      return result;
    } catch (error) {
      console.log(error);
    }
  };

  const handleInfoChange = (e: React.ChangeEvent) => {
    setPlayListInfo({
      ...playlistInfo,
      [e.target.name]: e.target.value,
    });
  };
  const handleImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const target = event.target as HTMLInputElement;
    if (target.files) {
      const file = target.files[0];
      if (file) {
        const imageUrl = URL.createObjectURL(file);
        setImageUrl(imageUrl);
        setImageFile(file);
        target.value = "";
      }
    }
  };

  const handleSubmitClick = async () => {
    try {
      const newPlaylist = await sendInfo();
      setPlayListInfo({
        name: "",
        description: "",
      });
      setImageFile(undefined);
      setImageUrl("");

      modalClose(false);
      setPlaylistList((prevState) => [...prevState, newPlaylist]);
    } catch (error) {
      console.log(error);
    }
  };
  return (
    <div
      className={clsx(styles.modal_wrapper, {
        [styles.modal_active]: active,
      })}
    >
      <div className={styles.modal}>
        <svg
          onClick={() => modalClose(false)}
          className={styles.close}
          width="30pt"
          height="30pt"
          viewBox="0 0 511.995 511.995"
        >
          <path
            d="M437.126,74.939c-99.826-99.826-262.307-99.826-362.133,0C26.637,123.314,0,187.617,0,256.005
            s26.637,132.691,74.993,181.047c49.923,49.923,115.495,74.874,181.066,74.874s131.144-24.951,181.066-74.874
            C536.951,337.226,536.951,174.784,437.126,74.939z M409.08,409.006c-84.375,84.375-221.667,84.375-306.042,0
            c-40.858-40.858-63.37-95.204-63.37-153.001s22.512-112.143,63.37-153.021c84.375-84.375,221.667-84.355,306.042,0
            C493.435,187.359,493.435,324.651,409.08,409.006z"
          />

          <path
            d="M341.525,310.827l-56.151-56.071l56.151-56.071c7.735-7.735,7.735-20.29,0.02-28.046
            c-7.755-7.775-20.31-7.755-28.065-0.02l-56.19,56.111l-56.19-56.111c-7.755-7.735-20.31-7.755-28.065,0.02
            c-7.735,7.755-7.735,20.31,0.02,28.046l56.151,56.071l-56.151,56.071c-7.755,7.735-7.755,20.29-0.02,28.046
            c3.868,3.887,8.965,5.811,14.043,5.811s10.155-1.944,14.023-5.792l56.19-56.111l56.19,56.111
            c3.868,3.868,8.945,5.792,14.023,5.792c5.078,0,10.175-1.944,14.043-5.811C349.28,331.117,349.28,318.562,341.525,310.827z"
          />
        </svg>
        <div className={styles.title}>
          <img
            src="/logo/logo-happy-1000.png"
            className={styles.pic}
            alt="logo"
          />
          <span className={styles.text}>Create the playlist</span>
        </div>
        <form className={styles.form}>
          <div className={styles.info}>
            <label htmlFor="upload" className={styles.avatar}>
              <div className={styles.picture}>
                <img
                  width={300}
                  height={300}
                  className={styles.image}
                  src={
                    imageUrl != ""
                      ? imageUrl
                      : "/defaults/playlist-default.jpeg"
                  }
                  alt="avatar"
                />
              </div>
              <div className={styles.text}>
                <span> Choose the avatar</span>
              </div>
            </label>
            <input
              onChange={handleImageChange}
              id="upload"
              className={styles.upload}
              type="file"
              name="avatar"
            />
            <div className={styles.input_info}>
              <div className={styles.input_block}>
                <span className={styles.input_title}>
                  Enter the name of your new playlist:
                </span>
                <input
                  name="name"
                  placeholder="Name..."
                  className={clsx(styles.input, styles.input_name)}
                  type="text"
                  value={playlistInfo.name}
                  onChange={handleInfoChange}
                ></input>
              </div>
              <div className={styles.input_block}>
                <span className={styles.input_title}>
                  Enter the description of your new playlist:
                </span>
                <textarea
                  name="description"
                  value={playlistInfo.description}
                  placeholder="Description..."
                  className={clsx(styles.input, styles.descr)}
                  onChange={handleInfoChange}
                ></textarea>
              </div>
            </div>
          </div>
          <Button
            onClick={handleSubmitClick}
            color={["white", "#a406cb", "none"]}
            size={[200, 50]}
            className={styles.submit}
          >
            Lets go
          </Button>
        </form>
      </div>
    </div>
  );
};

export default PlaylistModal;

组件Playlist.tsx

const Playlist: React.FC<PlaylistProps> = ({ name, avatar }) => {
  const handlePlaylistClick = (e: React.MouseEvent<HTMLLIElement>) => {
    if (e.target.tagName !== "svg" && e.target.tagName !== "path") {
      alert(`Playlist named '${name}'`);
    }
  };
  const handleEditClick = () => {
    alert(`Edit '${name}'`);
  };

  const handleDeleteClick = (e: React.MouseEvent<SVGSVGElement>) => {
    alert(`Delete '${name}'`);
  };

  return (
    <li onClick={handlePlaylistClick} className={styles.playlist}>
      <Image
        className={styles.avatar}
        width={50}
        height={50}
        src={require(`/server/avatars/playlists/${avatar}`)}
        alt="playlist-avatar"
      />
      <span className={styles.name}>{name}</span>
      <div className={styles.tools}>
        <svg
          onClick={handleEditClick}
          width="30pt"
          height="30pt"
          className={styles.tool}
          viewBox="-15 -15 484.00019 484"
        >
          <path d="m401.648438 18.234375c-24.394532-24.351563-63.898438-24.351563-88.292969 0l-22.101563 22.222656-235.269531 235.144531-.5.503907c-.121094.121093-.121094.25-.25.25-.25.375-.625.746093-.871094 1.121093 0 .125-.128906.125-.128906.25-.25.375-.371094.625-.625 1-.121094.125-.121094.246094-.246094.375-.125.375-.25.625-.378906 1 0 .121094-.121094.121094-.121094.25l-52.199219 156.96875c-1.53125 4.46875-.367187 9.417969 2.996094 12.734376 2.363282 2.332031 5.550782 3.636718 8.867188 3.625 1.355468-.023438 2.699218-.234376 3.996094-.625l156.847656-52.324219c.121094 0 .121094 0 .25-.121094.394531-.117187.773437-.285156 1.121094-.503906.097656-.011719.183593-.054688.253906-.121094.371094-.25.871094-.503906 1.246094-.753906.371093-.246094.75-.621094 1.125-.871094.125-.128906.246093-.128906.246093-.25.128907-.125.378907-.246094.503907-.5l257.371093-257.371094c24.351563-24.394531 24.351563-63.898437 0-88.289062zm-232.273438 353.148437-86.914062-86.910156 217.535156-217.535156 86.914062 86.910156zm-99.15625-63.808593 75.929688 75.925781-114.015626 37.960938zm347.664062-184.820313-13.238281 13.363282-86.917969-86.917969 13.367188-13.359375c14.621094-14.609375 38.320312-14.609375 52.945312 0l33.964844 33.964844c14.511719 14.6875 14.457032 38.332031-.121094 52.949218zm0 0" />
        </svg>
        <svg
          onClick={handleDeleteClick}
          width="30pt"
          height="30pt"
          className={styles.tool}
          viewBox="-40 0 427 427.00131"
        >
          <path d="m232.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0" />
          <path d="m114.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0" />
          <path d="m28.398438 127.121094v246.378906c0 14.5625 5.339843 28.238281 14.667968 38.050781 9.285156 9.839844 22.207032 15.425781 35.730469 15.449219h189.203125c13.527344-.023438 26.449219-5.609375 35.730469-15.449219 9.328125-9.8125 14.667969-23.488281 14.667969-38.050781v-246.378906c18.542968-4.921875 30.558593-22.835938 28.078124-41.863282-2.484374-19.023437-18.691406-33.253906-37.878906-33.257812h-51.199218v-12.5c.058593-10.511719-4.097657-20.605469-11.539063-28.03125-7.441406-7.421875-17.550781-11.5546875-28.0625-11.46875h-88.796875c-10.511719-.0859375-20.621094 4.046875-28.0625 11.46875-7.441406 7.425781-11.597656 17.519531-11.539062 28.03125v12.5h-51.199219c-19.1875.003906-35.394531 14.234375-37.878907 33.257812-2.480468 19.027344 9.535157 36.941407 28.078126 41.863282zm239.601562 279.878906h-189.203125c-17.097656 0-30.398437-14.6875-30.398437-33.5v-245.5h250v245.5c0 18.8125-13.300782 33.5-30.398438 33.5zm-158.601562-367.5c-.066407-5.207031 1.980468-10.21875 5.675781-13.894531 3.691406-3.675781 8.714843-5.695313 13.925781-5.605469h88.796875c5.210937-.089844 10.234375 1.929688 13.925781 5.605469 3.695313 3.671875 5.742188 8.6875 5.675782 13.894531v12.5h-128zm-71.199219 32.5h270.398437c9.941406 0 18 8.058594 18 18s-8.058594 18-18 18h-270.398437c-9.941407 0-18-8.058594-18-18s8.058593-18 18-18zm0 0" />
          <path d="m173.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0" />
        </svg>
      </div>
    </li>
  );
};

export default Playlist;

向服务器请求

createPlaylist: async(_info: FormData) => {
            try {
                const { data } = await instance.post("/playlists/create", _info, {
                    headers: {
                        "Content-Type": "multipart/form-data",
                      }
                    } 
                );
                return data;
            } catch (error) {
                console.log(error);
            }      
        },

处理信息并发送到数据库

async createPlaylist(req: express.Request, res: express.Response) {
        if (req.file) {
            try {
                const { id } = req.user!.data;
                const { filename: image } = req.file 
                const filePath = req.file?.path;
                let newFileName: string;
                newFileName = image;
                await sharp(path.resolve(filePath)).resize(150, 150).toFormat('jpeg').toFile(path.resolve(req.file?.destination, "playlists", newFileName)),  (err: any) => {
                    if (err) {
                      throw err;
                    }
                }

                fs.unlinkSync(filePath);
                const obj = {
                    name: req.body.name,
                    description: req.body.description,
                    avatarUrl: newFileName,
                    songs: [],
                    private: false,
                    belongsTo: id,
                }
                const playlistInfo = await Playlist.create(obj);
                res.status(200).json(playlistInfo);
            } catch (error) {
                console.log(error);
                res.sendStatus(500);
            }

        } else {
            try {
                const { id } = req.user!.data;
                const obj = {
                    name: req.body.name,
                    description: req.body.description,
                    avatarUrl: "default.jpeg",
                    songs: [],
                    private: false,
                    belongsTo: id,
                };
                const playlistInfo = await Playlist.create(obj);
                res.status(200).json(await playlistInfo.toJSON())

            } catch (error) {
                console.log(error);
                res.sendStatus(500);
            }
            
        }
    }

我想,出现这个错误是因为用户的图片没有足够的时间上传到服务器文件夹,所以next/image组件实际上需要不存在的图片。 如何在服务器上等待图片上传,然后才更新页面?如果这不是问题,那会是什么?

最后,我通过将所有头像从“/server/avatars/playlists”文件夹替换为“public/avatars/playlists”文件夹并更改播放列表组件中的 src 来解决它

<Image
  className={styles.avatar}
  width={50}
  height={50}
  src={`/avatars/playlists/${avatar}`}
  alt="playlist-avatar"
/>