next.js: next/image not working when using in esm projects

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.5.0: Thu Jun  8 22:22:20 PDT 2023; root:xnu-8796.121.3~7/RELEASE_ARM64_T6000
    Binaries:
      Node: 18.17.1
      npm: 9.6.7
      Yarn: N/A
      pnpm: N/A
    Relevant Packages:
      next: 13.4.20-canary.12
      eslint-config-next: 13.4.19
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.2.2
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

No response

Link to the code that reproduces this issue or a replay of the bug

https://github.com/yanush/nextjs_issue_20230830

To Reproduce

  • Create a new nextjs app: npx create-next-app@latest

Using the following options:

  • TypeScript: No

  • EsLint: Yes

  • Tailwind CSS: No

  • src/ directory: No

  • App Router: Yes

  • import Alias: No

  • Edit package.json and add: "type": "module",

Edit next.config.js file to match esm:

/** @type {import('next').NextConfig} */
const nextConfig = {}

export default nextConfig;
  • try to run project using npm run dev

Describe the Bug

I get these errors: Warning: React.jsx: type is invalid – expected a string (for built-in components) or a class/function (for composite components) but got: object.

Check your code at page.js:19. Warning: React.jsx: type is invalid – expected a string (for built-in components) or a class/function (for composite components) but got: object.

The errors referring to lines that contain Image Tags

Notice:

  • Using the legacy Image works fine (Changing page.js import to: import Image from 'next/legacy/image')

  • Changing the project to commonjs also seems to solve the issue

Both of these fixes are not ideal for me

Expected Behavior

Should be working fine with the new image and don’t throw errors

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1774

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Reactions: 1
  • Comments: 17 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Would be amazing if full ESM support was fixed in Next.js 👍 Maybe Vercel would sponsor this (considerable) effort

Originally, "type": "module" support was added by @timneutkens in this PR:

Basic support for ESM with TypeScript 'NodeNext' or 'Node16' module resolution added by @loettz here:

But since then, there have been a number of issues, pull requests and discussions showing problems with ESM support (sometimes in combination with the TypeScript 'NodeNext' or 'Node16' module resolution), just a small sampling:

cc @kachkaev @cseas because you have also been active in some issues and discussions

While I personally prefer named exports, I also believe there should be only one way to use an export. Adding a named export is a workaround. There is a deeper problem, this issue is just a symptom. Also it affects pretty much all Next.js exports, not just next/image.

A library (defined as something you import, e.g. next), should not rely on esModuleInterop if it’s compiled to CommonJS.

What really needs to happen, is that code like this:

import React from 'react'

export function unstable_getImgProps() {
  // …
}

export interface ImageProps {
  // …
}

export default function Image(props: ImageProps): React.ReactElement {
  // …
}

needs to be transformed to export = syntax and a namespace.

import * as React from 'react'

namespace Image {
  export function unstable_getImgProps() {
    // …
  }

  export interface ImageProps {
    // …
  }

  // This is for strict backwards compatibility. It’s not needed for typical Next.js users.
  export { Image as default }
}

function Image(props: ImageProps): React.ReactElement {
  // …
}

export = Image

Or go a step further and enable verbatimModuleSyntax:

import React = require('react')

namespace Image {
  export function unstable_getImgProps() {
    // …
  }

  export interface ImageProps {
    // …
  }

  // This is for strict backwards compatibility. It’s not needed for typical Next.js users.
  export { Image as default }
}

function Image(props: ImageProps): React.ReactElement {
  // …
}

export = Image

P.s. If Vercel is invested in supporting ESM/TypeScript (which means fixing this), I am for hire to fix this on a freelance contract.

@huozhi thanks for the fix in #59852

I can confirm that next@14.0.5-canary.25 resolves the Unsupported Server Component type error, at least with next/image 🙌 :


Broken with next@14.0.5-canary.24 (Unsupported Server Component type error)

CodeSandbox demo: https://codesandbox.io/p/devbox/esm-js-import-bug-w-next-image-14-0-5-canary-24-773jvq?file=%2Fpnpm-lock.yaml%3A10%2C62

Screenshot 2023-12-24 at 16 57 48


Working with next@14.0.5-canary.25

CodeSandbox demo: https://codesandbox.io/p/devbox/esm-js-import-bug-w-next-image-14-0-5-canary-25-x58yz8?file=%2Fpnpm-lock.yaml%3A10%2C62

Screenshot 2023-12-24 at 17 06 05

Just thinking, one way to get compatibility here without a larger CJS -> ESM transition would be to *also* export a named export for these cases.

Then the following would “just work” in ESM with "type": "module" projects:

import { Image } from 'next/image';

To be clear, for users who want to stay with the default export, the following would still work:

import Image from 'next/image';

cc @feedthejim @shuding would the team be open for such a change? (backwards compatible)

cc @remcohaszing because we were talking about lacking ESM support in Next.js and using named exports

@karlhorky, Thanks for all your suggestions and workaround attempts!