Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2276,29 +2276,50 @@ export default async function getBaseWebpackConfig(
}
}

if (!config.images.disableStaticImages) {
const rules = webpackConfig.module?.rules || []
const hasCustomSvg = rules.some(
(rule) =>
rule &&
const rules = webpackConfig.module?.rules || []

const customSvgRule = rules.find(
(rule): rule is webpack.RuleSetRule =>
(rule &&
typeof rule === 'object' &&
rule.loader !== 'next-image-loader' &&
'test' in rule &&
rule.test instanceof RegExp &&
rule.test.test('.svg')
)
rule.test.test('.svg')) ||
false
)

if (customSvgRule && hasAppDir) {
// Create React aliases for SVG components that were transformed using a
// custom webpack config with e.g. the `@svgr/webpack` loader, or the
// `babel-plugin-inline-react-svg` plugin.
rules.push({
test: customSvgRule.test,
oneOf: [
WEBPACK_LAYERS.reactServerComponents,
WEBPACK_LAYERS.serverSideRendering,
WEBPACK_LAYERS.appPagesBrowser,
].map((layer) => ({
issuerLayer: layer,
resolve: {
alias: createRSCAliases(bundledReactChannel, {
reactProductionProfiling,
layer,
isEdgeServer,
}),
},
})),
})
}

if (!config.images.disableStaticImages) {
const nextImageRule = rules.find(
(rule) =>
rule && typeof rule === 'object' && rule.loader === 'next-image-loader'
)
if (
hasCustomSvg &&
nextImageRule &&
nextImageRule &&
typeof nextImageRule === 'object'
) {
if (customSvgRule && nextImageRule && typeof nextImageRule === 'object') {
// Exclude svg if the user already defined it in custom
// webpack config such as `@svgr/webpack` plugin or
// webpack config such as the `@svgr/webpack` loader, or
// the `babel-plugin-inline-react-svg` plugin.
nextImageRule.test = /\.(png|jpg|jpeg|gif|webp|avif|ico|bmp)$/i
}
Expand Down
9 changes: 9 additions & 0 deletions test/e2e/app-dir/react-owner-stacks-svgr/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReactNode } from 'react'

export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
5 changes: 5 additions & 0 deletions test/e2e/app-dir/react-owner-stacks-svgr/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Icon from '../public/test.svg'

export default function Page() {
return <Icon />
}
25 changes: 25 additions & 0 deletions test/e2e/app-dir/react-owner-stacks-svgr/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
// Enabling PPR to force using the react experimental channel, which
// implements React owner stacks.
ppr: true,
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
},
webpack(config) {
config.module.rules.push({ test: /\.svg$/, use: '@svgr/webpack' })

return config
},
}

module.exports = nextConfig
10 changes: 10 additions & 0 deletions test/e2e/app-dir/react-owner-stacks-svgr/public/test.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { nextTestSetup } from 'e2e-utils'

describe('react-owner-stacks-svgr', () => {
const { next, isNextStart, isTurbopack } = nextTestSetup({
files: __dirname,
packageJson: { dependencies: { '@svgr/webpack': '8.1.0' } },
})

/* eslint-disable jest/no-standalone-expect */
// Turbopack currently only supports `next dev` and does not support `next
// build`: https://nextjs.org/docs/architecture/turbopack#unsupported-features
;(isNextStart && isTurbopack ? it.skip : it)(
'renders an SVG that is transformed by @svgr/webpack into a React component',
async () => {
const browser = await next.browser('/')
expect(await browser.elementByCss('svg')).toBeDefined()
}
)
/* eslint-enable jest/no-standalone-expect */
})