diff --git a/packages/react-refresh-utils/internal/helpers.ts b/packages/react-refresh-utils/internal/helpers.ts
index de8728308a53bd..4c36c0d2e00a36 100644
--- a/packages/react-refresh-utils/internal/helpers.ts
+++ b/packages/react-refresh-utils/internal/helpers.ts
@@ -44,7 +44,13 @@ declare const module: {
}
function isSafeExport(key: string): boolean {
- return key === '__esModule' || key === '__N_SSG' || key === '__N_SSP'
+ return (
+ key === '__esModule' ||
+ key === '__N_SSG' ||
+ key === '__N_SSP' ||
+ // TODO: remove this key from page config instead of allow listing it
+ key === 'config'
+ )
}
function registerExportsForReactRefresh(
diff --git a/test/acceptance/ReactRefreshRegression.test.js b/test/acceptance/ReactRefreshRegression.test.js
index fd7d98d1e9ef4e..3f33cebd989ffa 100644
--- a/test/acceptance/ReactRefreshRegression.test.js
+++ b/test/acceptance/ReactRefreshRegression.test.js
@@ -67,3 +67,190 @@ test('styled-components hydration mismatch', async () => {
await cleanup()
})
+
+// https://github.com/vercel/next.js/issues/13978
+test('can fast refresh a page with getStaticProps', async () => {
+ const [session, cleanup] = await sandbox()
+
+ await session.patch(
+ 'pages/index.js',
+ `
+ import { useCallback, useState } from 'react'
+
+ export function getStaticProps() {
+ return { props: { } }
+ }
+
+ export default function Index() {
+ const [count, setCount] = useState(0)
+ const increment = useCallback(() => setCount(c => c + 1), [setCount])
+ return (
+
+ {count}
+
+
+ )
+ }
+ `
+ )
+
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('0')
+ await session.evaluate(() => document.querySelector('button').click())
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('1')
+
+ await session.patch(
+ 'pages/index.js',
+ `
+ import { useCallback, useState } from 'react'
+
+ export default function Index() {
+ const [count, setCount] = useState(0)
+ const increment = useCallback(() => setCount(c => c + 1), [setCount])
+ return (
+
+ Count: {count}
+
+
+ )
+ }
+ `
+ )
+
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('Count: 1')
+ await session.evaluate(() => document.querySelector('button').click())
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('Count: 2')
+
+ await cleanup()
+})
+
+// https://github.com/vercel/next.js/issues/13978
+test('can fast refresh a page with getServerSideProps', async () => {
+ const [session, cleanup] = await sandbox()
+
+ await session.patch(
+ 'pages/index.js',
+ `
+ import { useCallback, useState } from 'react'
+
+ export function getServerSideProps() {
+ return { props: { } }
+ }
+
+ export default function Index() {
+ const [count, setCount] = useState(0)
+ const increment = useCallback(() => setCount(c => c + 1), [setCount])
+ return (
+
+ {count}
+
+
+ )
+ }
+ `
+ )
+
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('0')
+ await session.evaluate(() => document.querySelector('button').click())
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('1')
+
+ await session.patch(
+ 'pages/index.js',
+ `
+ import { useCallback, useState } from 'react'
+
+ export default function Index() {
+ const [count, setCount] = useState(0)
+ const increment = useCallback(() => setCount(c => c + 1), [setCount])
+ return (
+
+ Count: {count}
+
+
+ )
+ }
+ `
+ )
+
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('Count: 1')
+ await session.evaluate(() => document.querySelector('button').click())
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('Count: 2')
+
+ await cleanup()
+})
+
+// https://github.com/vercel/next.js/issues/13978
+test('can fast refresh a page with config', async () => {
+ const [session, cleanup] = await sandbox()
+
+ await session.patch(
+ 'pages/index.js',
+ `
+ import { useCallback, useState } from 'react'
+
+ export const config = {}
+
+ export default function Index() {
+ const [count, setCount] = useState(0)
+ const increment = useCallback(() => setCount(c => c + 1), [setCount])
+ return (
+
+ {count}
+
+
+ )
+ }
+ `
+ )
+
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('0')
+ await session.evaluate(() => document.querySelector('button').click())
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('1')
+
+ await session.patch(
+ 'pages/index.js',
+ `
+ import { useCallback, useState } from 'react'
+
+ export default function Index() {
+ const [count, setCount] = useState(0)
+ const increment = useCallback(() => setCount(c => c + 1), [setCount])
+ return (
+
+ Count: {count}
+
+
+ )
+ }
+ `
+ )
+
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('Count: 1')
+ await session.evaluate(() => document.querySelector('button').click())
+ expect(
+ await session.evaluate(() => document.querySelector('p').textContent)
+ ).toBe('Count: 2')
+
+ await cleanup()
+})