diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts
index b807fe24926fe..bd99853ee2534 100644
--- a/packages/next/build/webpack/config/blocks/css/index.ts
+++ b/packages/next/build/webpack/config/blocks/css/index.ts
@@ -104,6 +104,7 @@ export const css = curry(async function css(
options: {
importLoaders: 1,
sourceMap: true,
+ onlyLocals: ctx.isServer,
modules: {
// Disallow global style exports so we can code-split CSS and
// not worry about loading order.
diff --git a/test/integration/css/fixtures/single-module/pages/index.js b/test/integration/css/fixtures/single-module/pages/index.js
new file mode 100644
index 0000000000000..79140b6b136c9
--- /dev/null
+++ b/test/integration/css/fixtures/single-module/pages/index.js
@@ -0,0 +1,11 @@
+import { redText } from './index.module.css'
+
+function Home() {
+ return (
+
+ This text should be red.
+
+ )
+}
+
+export default Home
diff --git a/test/integration/css/fixtures/single-module/pages/index.module.css b/test/integration/css/fixtures/single-module/pages/index.module.css
new file mode 100644
index 0000000000000..08a38e09ef8ea
--- /dev/null
+++ b/test/integration/css/fixtures/single-module/pages/index.module.css
@@ -0,0 +1,3 @@
+.redText {
+ color: red;
+}
diff --git a/test/integration/css/test/index.test.js b/test/integration/css/test/index.test.js
index fe94594d59275..3a817c135d481 100644
--- a/test/integration/css/test/index.test.js
+++ b/test/integration/css/test/index.test.js
@@ -203,19 +203,19 @@ describe('CSS Support', () => {
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
- Object {
- "mappings": "AAAA,+CACE,4BACE,WACF,CAFA,mBACE,WACF,CAFA,uBACE,WACF,CAFA,wBACE,WACF,CAFA,cACE,WACF,CACF",
- "sourcesContent": Array [
- "@media (480px <= width < 768px) {
- ::placeholder {
- color: green;
- }
- }
- ",
- ],
- "version": 3,
- }
- `)
+ Object {
+ "mappings": "AAAA,+CACE,4BACE,WACF,CAFA,mBACE,WACF,CAFA,uBACE,WACF,CAFA,wBACE,WACF,CAFA,cACE,WACF,CACF",
+ "sourcesContent": Array [
+ "@media (480px <= width < 768px) {
+ ::placeholder {
+ color: green;
+ }
+ }
+ ",
+ ],
+ "version": 3,
+ }
+ `)
})
})
@@ -261,25 +261,25 @@ describe('CSS Support', () => {
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
- Object {
- "mappings": "AACA,gCACE,cACE,WACF,CACF,CAGA,OACE,eAA0B,CAA1B,gBACF",
- "sourcesContent": Array [
- "/* this should pass through untransformed */
- @media (480px <= width < 768px) {
- ::placeholder {
- color: green;
- }
- }
-
- /* this should be transformed to width/height */
- .video {
- -xyz-max-size: 400px 300px;
- }
- ",
- ],
- "version": 3,
- }
- `)
+ Object {
+ "mappings": "AACA,gCACE,cACE,WACF,CACF,CAGA,OACE,eAA0B,CAA1B,gBACF",
+ "sourcesContent": Array [
+ "/* this should pass through untransformed */
+ @media (480px <= width < 768px) {
+ ::placeholder {
+ color: green;
+ }
+ }
+
+ /* this should be transformed to width/height */
+ .video {
+ -xyz-max-size: 400px 300px;
+ }
+ ",
+ ],
+ "version": 3,
+ }
+ `)
})
})
@@ -674,12 +674,12 @@ describe('CSS Support', () => {
)
.sort()
).toMatchInlineSnapshot(`
- Array [
- "dark.svg",
- "dark2.svg",
- "light.svg",
- ]
- `)
+ Array [
+ "dark.svg",
+ "dark2.svg",
+ "light.svg",
+ ]
+ `)
})
})
@@ -800,4 +800,60 @@ describe('CSS Support', () => {
expect(cssMapFiles.length).toBe(1)
})
})
+
+ describe('Basic CSS Module Support', () => {
+ const appDir = join(fixturesDir, 'single-module')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await nextBuild(appDir)
+ const server = nextServer({
+ dir: appDir,
+ dev: false,
+ quiet: true,
+ })
+
+ app = await startApp(server)
+ appPort = app.address().port
+ })
+ afterAll(async () => {
+ await stopApp(app)
+ })
+
+ it(`should've emitted a single CSS file`, async () => {
+ const cssFolder = join(appDir, '.next/static/css')
+
+ const files = await readdir(cssFolder)
+ const cssFiles = files.filter(f => /\.css$/.test(f))
+
+ expect(cssFiles.length).toBe(1)
+ const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
+
+ expect(
+ cssContent.replace(/\/\*.*?\*\//g, '').trim()
+ ).toMatchInlineSnapshot(`".index_redText__3CwEB{color:red}"`)
+ })
+
+ it(`should've injected the CSS on server render`, async () => {
+ const content = await renderViaHTTP(appPort, '/')
+ const $ = cheerio.load(content)
+
+ const cssPreload = $('link[rel="preload"][as="style"]')
+ expect(cssPreload.length).toBe(1)
+ expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ const cssSheet = $('link[rel="stylesheet"]')
+ expect(cssSheet.length).toBe(1)
+ expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ expect($('#verify-red').attr('class')).toMatchInlineSnapshot(
+ `"index_redText__3CwEB"`
+ )
+ })
+ })
})