Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pathname and search params SSG #41247

Merged
merged 6 commits into from Oct 25, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 1 addition & 17 deletions packages/next/client/components/headers.ts
@@ -1,21 +1,5 @@
import { DynamicServerError } from './hooks-server-context'
import { requestAsyncStorage } from './request-async-storage'
import { staticGenerationAsyncStorage } from './static-generation-async-storage'

function staticGenerationBailout(reason: string) {
const staticGenerationStore =
staticGenerationAsyncStorage && 'getStore' in staticGenerationAsyncStorage
? staticGenerationAsyncStorage?.getStore()
: staticGenerationAsyncStorage

if (staticGenerationStore?.isStaticGeneration) {
// TODO: honor the dynamic: 'force-static'
if (staticGenerationStore) {
staticGenerationStore.revalidate = 0
}
throw new DynamicServerError(reason)
}
}
import { staticGenerationBailout } from './static-generation-bailout'

export function headers() {
staticGenerationBailout('headers')
Expand Down
3 changes: 3 additions & 0 deletions packages/next/client/components/navigation.ts
Expand Up @@ -12,6 +12,7 @@ import {
PathnameContext,
// LayoutSegmentsContext,
} from './hooks-client-context'
import { staticGenerationBailout } from './static-generation-bailout'

const INTERNAL_URLSEARCHPARAMS_INSTANCE = Symbol(
'internal for urlsearchparams readonly'
Expand Down Expand Up @@ -69,6 +70,7 @@ class ReadonlyURLSearchParams {
* Learn more about URLSearchParams here: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
*/
export function useSearchParams() {
staticGenerationBailout('useSearchParams')
const searchParams = useContext(SearchParamsContext)
const readonlySearchParams = useMemo(() => {
return new ReadonlyURLSearchParams(searchParams)
Expand All @@ -80,6 +82,7 @@ export function useSearchParams() {
* Get the current pathname. For example usePathname() on /dashboard?foo=bar would return "/dashboard"
*/
export function usePathname(): string {
staticGenerationBailout('usePathname')
return useContext(PathnameContext)
}

Expand Down
17 changes: 17 additions & 0 deletions packages/next/client/components/static-generation-bailout.ts
@@ -0,0 +1,17 @@
import { DynamicServerError } from './hooks-server-context'
import { staticGenerationAsyncStorage } from './static-generation-async-storage'

export function staticGenerationBailout(reason: string) {
const staticGenerationStore =
staticGenerationAsyncStorage && 'getStore' in staticGenerationAsyncStorage
? staticGenerationAsyncStorage?.getStore()
: staticGenerationAsyncStorage

if (staticGenerationStore?.isStaticGeneration) {
// TODO: honor the dynamic: 'force-static'
if (staticGenerationStore) {
staticGenerationStore.revalidate = 0
}
throw new DynamicServerError(reason)
}
}
82 changes: 78 additions & 4 deletions test/e2e/app-dir/app-static.test.ts
Expand Up @@ -10,6 +10,8 @@ import webdriver from 'next-webdriver'
const glob = promisify(globOrig)

describe('app-dir static/dynamic handling', () => {
const isDev = (global as any).isNextDev

if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
Expand Down Expand Up @@ -56,6 +58,8 @@ describe('app-dir static/dynamic handling', () => {
'blog/tim/first-post.rsc',
'dynamic-no-gen-params-ssr/[slug]/page.js',
'dynamic-no-gen-params/[slug]/page.js',
'hooks/use-pathname/[slug]/page.js',
'hooks/use-search-params/[slug]/page.js',
'ssr-auto/cache-no-store/page.js',
'ssr-auto/fetch-revalidate-zero/page.js',
'ssr-forced/page.js',
Expand Down Expand Up @@ -359,9 +363,79 @@ describe('app-dir static/dynamic handling', () => {
expect(secondDate).not.toBe(initialDate)
})

it('should show a message to leave feedback for `appDir`', async () => {
expect(next.cliOutput).toContain(
`Thank you for testing \`appDir\` please leave your feedback at https://nextjs.link/app-feedback`
)
describe('hooks', () => {
describe('useSearchParams', () => {
if (isDev) {
it('should bail out to client rendering during SSG', async () => {
const res = await fetchViaHTTP(
next.url,
'/hooks/use-search-params/slug'
)
const html = await res.text()
expect(html).toInclude('<html id="__next_error__">')
})
}

it('should have the correct values', async () => {
const browser = await webdriver(
next.url,
'/hooks/use-search-params/slug?first=value&second=other&third'
)

expect(await browser.elementByCss('#params-first').text()).toBe('value')
expect(await browser.elementByCss('#params-second').text()).toBe(
'other'
)
expect(await browser.elementByCss('#params-third').text()).toBe('')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})

it('should have values from canonical url on rewrite', async () => {
const browser = await webdriver(
next.url,
'/rewritten-use-search-params?first=a&second=b&third=c'
)

expect(await browser.elementByCss('#params-first').text()).toBe('a')
expect(await browser.elementByCss('#params-second').text()).toBe('b')
expect(await browser.elementByCss('#params-third').text()).toBe('c')
expect(await browser.elementByCss('#params-not-real').text()).toBe(
'N/A'
)
})
})

describe('usePathname', () => {
if (isDev) {
it('should bail out to client rendering during SSG', async () => {
const res = await fetchViaHTTP(next.url, '/hooks/use-pathname/slug')
const html = await res.text()
expect(html).toInclude('<html id="__next_error__">')
})
}

it('should have the correct values', async () => {
const browser = await webdriver(next.url, '/hooks/use-pathname/slug')

expect(await browser.elementByCss('#pathname').text()).toBe(
'/hooks/use-pathname/slug'
)
})

it('should have values from canonical url on rewrite', async () => {
const browser = await webdriver(next.url, '/rewritten-use-pathname')

expect(await browser.elementByCss('#pathname').text()).toBe(
'/rewritten-use-pathname'
)
})
})
it('should show a message to leave feedback for `appDir`', async () => {
expect(next.cliOutput).toContain(
`Thank you for testing \`appDir\` please leave your feedback at https://nextjs.link/app-feedback`
)
})
})
})
@@ -0,0 +1,7 @@
export default function Layout({ children }) {
return children
}

export function generateStaticParams() {
return [{ slug: 'slug' }]
}
@@ -0,0 +1,12 @@
'use client'
import { usePathname } from 'next/navigation'

export const config = {
dynamicParams: false,
}

export default function Page() {
const pathname = usePathname()

return <p id="pathname">{pathname}</p>
}
@@ -0,0 +1,7 @@
export default function Layout({ children }) {
return children
}

export function generateStaticParams() {
return [{ slug: 'slug' }]
}
@@ -0,0 +1,19 @@
'use client'
import { useSearchParams } from 'next/navigation'

export const config = {
dynamicParams: false,
}

export default function Page() {
const params = useSearchParams()

return (
<>
<p id="params-first">{params.get('first') ?? 'N/A'}</p>
<p id="params-second">{params.get('second') ?? 'N/A'}</p>
<p id="params-third">{params.get('third') ?? 'N/A'}</p>
<p id="params-not-real">{params.get('notReal') ?? 'N/A'}</p>
</>
)
}
9 changes: 9 additions & 0 deletions test/e2e/app-dir/app-static/next.config.js
Expand Up @@ -11,6 +11,15 @@ module.exports = {
source: '/rewritten-to-dashboard',
destination: '/dashboard',
},
{
source: '/rewritten-use-search-params',
destination:
'/hooks/use-search-params/slug?first=value&second=other%20value&third',
},
{
source: '/rewritten-use-pathname',
destination: '/hooks/use-pathname/slug',
},
],
}
},
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app/app/hooks/use-pathname/page.js
Expand Up @@ -8,7 +8,7 @@ export default function Page() {
return (
<>
<h1 id="pathname" data-pathname={pathname}>
hello from /hooks/use-pathname
hello from {pathname}
</h1>
</>
)
Expand Down
13 changes: 4 additions & 9 deletions test/e2e/app-dir/app/app/hooks/use-search-params/page.js
Expand Up @@ -7,15 +7,10 @@ export default function Page() {

return (
<>
<h1
id="params"
data-param-first={params.get('first') ?? 'N/A'}
data-param-second={params.get('second') ?? 'N/A'}
data-param-third={params.get('third') ?? 'N/A'}
data-param-not-real={params.get('notReal') ?? 'N/A'}
>
hello from /hooks/use-search-params
</h1>
<p id="params-first">{params.get('first') ?? 'N/A'}</p>
<p id="params-second">{params.get('second') ?? 'N/A'}</p>
<p id="params-third">{params.get('third') ?? 'N/A'}</p>
<p id="params-not-real">{params.get('notReal') ?? 'N/A'}</p>
</>
)
}
15 changes: 15 additions & 0 deletions test/e2e/app-dir/app/app/search-params-prop/page.js
@@ -0,0 +1,15 @@
'use client'

export default function Page({ searchParams }) {
return (
<h1
id="params"
data-param-first={searchParams.first ?? 'N/A'}
data-param-second={searchParams.second ?? 'N/A'}
data-param-third={searchParams.third ?? 'N/A'}
data-param-not-real={searchParams.notReal ?? 'N/A'}
>
hello from searchParams prop client
</h1>
)
}
13 changes: 13 additions & 0 deletions test/e2e/app-dir/app/app/search-params-prop/server/page.js
@@ -0,0 +1,13 @@
export default function Page({ searchParams }) {
return (
<h1
id="params"
data-param-first={searchParams.first ?? 'N/A'}
data-param-second={searchParams.second ?? 'N/A'}
data-param-third={searchParams.third ?? 'N/A'}
data-param-not-real={searchParams.notReal ?? 'N/A'}
>
hello from searchParams prop server
</h1>
)
}
20 changes: 20 additions & 0 deletions test/e2e/app-dir/app/middleware.js
Expand Up @@ -42,4 +42,24 @@ export function middleware(request) {

return NextResponse[method](new URL('/internal/success', request.url))
}

if (request.nextUrl.pathname === '/search-params-prop-middleware-rewrite') {
return NextResponse.rewrite(
new URL(
'/search-params-prop?first=value&second=other%20value&third',
request.url
)
)
}

if (
request.nextUrl.pathname === '/search-params-prop-server-middleware-rewrite'
) {
return NextResponse.rewrite(
new URL(
'/search-params-prop/server?first=value&second=other%20value&third',
request.url
)
)
}
}
19 changes: 19 additions & 0 deletions test/e2e/app-dir/app/next.config.js
Expand Up @@ -12,6 +12,25 @@ module.exports = {
source: '/rewritten-to-dashboard',
destination: '/dashboard',
},
{
source: '/search-params-prop-rewrite',
destination:
'/search-params-prop?first=value&second=other%20value&third',
},
{
source: '/search-params-prop-server-rewrite',
destination:
'/search-params-prop/server?first=value&second=other%20value&third',
},
{
source: '/rewritten-use-search-params',
destination:
'/hooks/use-search-params?first=value&second=other%20value&third',
},
{
source: '/rewritten-use-pathname',
destination: '/hooks/use-pathname',
},
{
source: '/hooks/use-selected-layout-segment/rewritten',
destination:
Expand Down