Next.js '找不到模块'./filename.jpeg'。来不及上传图片

Posted

技术标签:

【中文标题】Next.js \'找不到模块\'./filename.jpeg\'。来不及上传图片【英文标题】:Next.js 'Cannot find module './filename.jpeg'. Not enough time to upload the imageNext.js '找不到模块'./filename.jpeg'。来不及上传图片 【发布时间】:2021-11-01 07:36:04 【问题描述】:

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

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

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

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

页面播放列表.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
                
              />
            </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(模态窗口)

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
          
          
          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
            
          />
          <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"
                  
                  
                />
              </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;

组件播放列表.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`)
        
      />
      <span className=styles.name>name</span>
      <div className=styles.tools>
        <svg
          onClick=handleEditClick
          
          
          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
          
          
          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组件实际上需要不存在的图片。 如何等待服务器上传图片,然后才更新页面?如果这不是问题,那会是什么?

【问题讨论】:

【参考方案1】:

最后,我解决了这个问题,将“/server/avatars/playlists”文件夹中的所有头像替换为“public/avatars/playlists”文件夹并更改播放列表组件中的src

<Image
  className=styles.avatar
  width=50
  height=50
  src=`/avatars/playlists/$avatar`
  
/>

【讨论】:

以上是关于Next.js '找不到模块'./filename.jpeg'。来不及上传图片的主要内容,如果未能解决你的问题,请参考以下文章

Next.js:找不到模块:无法解析“canvg”

解析错误:找不到模块'next/babel'

错误:在 next.js 中找不到模块“swiper/react”

从 JSON 文件动态加载图像位置 - (找不到模块...) React, Next.js

Storybook 在 React、Next.JS、Typescript 项目中找不到组件

找不到模块:错误:无法解析“/app/node_modules/next/dist/lib”中的“pnpapi”