Next.js构建报错:无法解析'../header/main-header.js'模块
Next.js构建报错:Module not found: Can't resolve '../header/main-header.js'
问题描述
在layout.js中导入MainHeader组件时,开发环境运行正常,但构建时出现如下错误:
"Module not found: Can't resolve '../header/main-header.js'"
导入代码:
import MainHeader from "../header/main-header.js";
main-header.js的导出逻辑正常:
export default MainHeader;
怀疑问题出在next.config.js的配置上,不清楚如何将const nextConfig与webpack配置结合。当前next.config.js代码如下:
/** @type {import('next').NextConfig} */ const nextConfig = { pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], reactStrictMode: true, }; module.exports = { webpack(config) { const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.(".svg")); config.module.rules.push( { ...fileLoaderRule, test: /\.svg$/i, resourceQuery: /url/, }, { test: /\.svg$/i, issuer: /\.[jt]sx?$/, resourceQuery: { not: /url/ }, use: ["@svgr/webpack"], } ); fileLoaderRule.exclude = /\.svg$/i; return config; }, };
尝试过去掉.js后缀导入、使用全局路径,均无法解决构建报错问题。
解决方案
1. 修复next.config.js的配置合并问题
当前代码错误地将webpack配置单独导出,未与nextConfig合并,导致Next.js默认的模块解析规则未生效。正确写法是将webpack配置作为nextConfig的属性:
/** @type {import('next').NextConfig} */ const nextConfig = { pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], reactStrictMode: true, webpack(config) { const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.(".svg")); config.module.rules.push( { ...fileLoaderRule, test: /\.svg$/i, resourceQuery: /url/, }, { test: /\.svg$/i, issuer: /\.[jt]sx?$/, resourceQuery: { not: /url/ }, use: ["@svgr/webpack"], } ); fileLoaderRule.exclude = /\.svg$/i; return config; }, }; module.exports = nextConfig;
这样既保留了自定义的SVG处理规则,又启用了nextConfig中的所有配置,确保模块解析时能正确识别.js后缀文件。
2. 验证文件路径与大小写一致性
开发环境可能不区分大小写,但构建环境(如Linux服务器)通常大小写敏感,需确认:
layout.js所在目录下存在header文件夹header文件夹内确实有main-header.js文件(注意是连字符-,不是点.)- 导入路径中的文件夹名、文件名大小写完全匹配
3. 清理缓存并重新构建
执行以下命令清理Next.js缓存,避免旧缓存导致的解析问题:
rm -rf .next npm run build
补充代码参考
layout.js
import MainHeader from "../header/main-header.js"; import { Inter } from "@next/font/google"; import Footer from "../footer/footer"; import { useRouter } from "next/router"; import ErrorPage from "@/pages/404"; import { useState, useEffect } from "react"; import SquareLoader from "react-spinners/SquareLoader"; const inter = Inter({ weight: ["300", "400", "500", "600", "700"], subsets: ["latin"], }); const Layout = (props) => { const router = useRouter(); const [pageLoading, setPageLoading] = useState(false); const storage = typeof window !== "undefined" ? window.localStorage.theme : "light"; const override = { display: "block", margin: "0 auto", borderRadius: "3px", borderColor: "blue", }; useEffect(() => { const handleStart = () => { setPageLoading(true); }; const handleComplete = () => { setPageLoading(false); }; router.events.on("routeChangeStart", handleStart); router.events.on("routeChangeComplete", handleComplete); router.events.on("routeChangeError", handleComplete); }, [router]); return router.pathname !== "/404" ? ( <div className={`${inter.className} ${ pageLoading ? "background__loading_theme" : "background-theme" }`} > <MainHeader /> {pageLoading ? ( <div className="loading-theme"> <SquareLoader color={storage === "light" ? "#2b2b2b" : "#ffffff"} loading={pageLoading} cssOverride={override} size={70} aria-label="Loading Spinner" data-testid="loader" /> </div> ) : ( <main className="background">{props.children}</main> )} <Footer /> </div> ) : ( <div className={`${inter.className} error-theme`}> <ErrorPage /> </div> ); }; export default Layout;
main-header.js
import Link from "next/link"; import { useState, useEffect } from "react"; import styles from "./main-header.module.css"; import menustyles from "./menu-icon.module.css"; import Image from "next/image"; import logo from "../../public/images/my-logo.png"; import logoDark from "../../public/images/my-logo-dark.png"; import SunIcon from "../../public/images/sun.svg"; import MoonIcon from "../../public/images/moon.svg"; import { useRouter } from "next/router"; import { useRef } from "react"; import MenuIcon from "./menu-icon"; const MainHeader = () => { const storage = typeof window !== "undefined" ? window.localStorage.theme : "light"; const [themeMode, setThemeMode] = useState(storage); const [clientLoaded, setClientLoaded] = useState(false); const themeModeHandle = (e) => { e.preventDefault(); setThemeMode((prev) => (prev === "dark" ? "light" : "dark")); }; const navRef = useRef(); const iconRef = useRef(); const sunIconRef = useRef(); const router = useRouter(); const handleAnimation = () => { iconRef.current.classList.toggle(`${menustyles.openmenu}`); }; const showNavbar = () => { navRef.current.classList.toggle(`${styles.responsive__nav}`); if (!sunIconRef.current.classList.contains(`${styles.responsive__icon}`)) { sunIconRef.current.classList.add(styles.responsive__icon); } }; const closeNavbar = () => { navRef.current.classList.remove(`${styles.responsive__nav}`); if (iconRef.current.classList.contains(`${menustyles.openmenu}`)) { handleAnimation(); } if (!sunIconRef.current.classList.contains(`${styles.responsive__icon}`)) { sunIconRef.current.classList.add(styles.responsive__icon); } else { sunIconRef.current.classList.remove(styles.responsive__icon); } }; useEffect(() => { document.body.dataset.theme = themeMode; window.localStorage.setItem("theme", themeMode); }, [themeMode]); useEffect(() => { setClientLoaded(true); }, []); return ( <header className={styles.header}> <div className={styles.header__main}> <div className={styles.header__menu}> <Link href="/"> {(themeMode === "light" || themeMode === undefined) && clientLoaded ? ( <Image src={logo} alt="website logo" className={styles.header__logo} priority={true} /> ) : ( <Image src={logoDark} alt="website logo" className={styles.header__logo} /> )} </Link> <nav className={styles.navigation} ref={navRef}> <ul className={styles.navigation__ul}> <li onClick={closeNavbar} className={router.asPath === "/" ? `${styles.li__active}` : ""} > <Link href="/">Portfolio</Link> </li> <li onClick={closeNavbar} className={router.asPath === "/about" ? `${styles.li__active}` : ""} > <Link href="/about">About</Link> </li> <li onClick={closeNavbar} className={router.asPath === "/blog" ? `${styles.li__active}` : ""} > <Link href="/blog">Blog</Link> </li> <li onClick={closeNavbar} className={router.asPath === "/projects" ? `${styles.li__active}` : ""} > <Link href="/projects">Projects</Link> </li> </ul> <button className={styles.navigation__modalicons}> {themeMode === "light" && clientLoaded ? ( <div ref={sunIconRef}> <SunIcon onClick={themeModeHandle} className={`${styles.navigation__sun} ${styles.sun} `} /> </div> ) : ( <div ref={sunIconRef}> <MoonIcon onClick={themeModeHandle} className={`${styles.navigation__sun} ${styles.moon} `} /> </div> )} </button> </nav> <button className={styles.navigation__btn} onClick={showNavbar} aria-label="Button menu icon" > <MenuIcon animation={handleAnimation} ref={iconRef} /> </button> </div> <button className={styles.navigation__icons} aria-label="Icon to change a theme of a website" > {themeMode === "light" && clientLoaded ? ( <SunIcon onClick={themeModeHandle} className={`${styles.header__sun} ${styles.sun}`} /> ) : ( <MoonIcon onClick={themeModeHandle} className={`${styles.header__sun} ${styles.moon}`} /> )} </button> </div> </header> ); }; export default MainHeader;
内容的提问来源于stack exchange,提问作者Roma Kim
相关产品推荐
相关产品推荐

