mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/ledgers-react-sync
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
05f159dffc |
[Breaking] Update Dynamic APIs to be async (#68812)
Next.js has a number of dynamic APIs that aren't available during
prerendering. What happens when you access these APIs might differ
depending on the mode you are using, for instance if you have PPR turned
on or the newly introduced `dynamicIO` experimental mode. But regardless
of the mode the underlying API represents accessing something that might
only be available at render time (dynamic rendering) rather than
prerender time (build and revalidate rendering)
Unfortunately our current dynamic APIs make certain kinds of modeling
tricky because they are all synchronous. For instance if we wanted to
add a feature to Next.js where we started a dynamic render before a
Request even hits the server it would be interesting to be able to start
working on everything that does not rely on any dynamic data and then
once a real Request arrives we can continue the render and provide the
associated Request context through our dynamic APIs.
If our dynamic APIs were all async we could build something like this
because they represnt a value that will eventually resolve to some
Request value. This PR updates most existing dynamic APIs to be async
rather than sync. This is a breaking change and will need to be paired
with codemods to realistically adopt. Additionally since this change is
so invasive I have implemented it in a way to maximize backward
compatibility by still allowing most synchronous access. The combination
of codemods, typescript updates, and backward compat functionality
should make it possible for projects to upgrade to the latest version
with minimal effort and then follow up with a complete conversion over
time.
#### `cookies()`
`cookies()` now returns `Promise<ReadonlyRequestCookies>`. Synchronous
access to the underlying RequestCookies object is still supported to
facilitate migration.
```tsx
// ------------ preferred usage
// async Server Component
const token = (await cookies()).get('token')
// sync Server Component
import { use } from 'react'
//...
const token = use(cookies()).get('token')
// ------------ temporarily allowed usage
// javascript, dev warning at runtime
const token = cookies().get('token')
// typescript, dev warning at runtime
import { type UnsafeUnwrappedCookies } from 'next/headers'
// ...
const token = (cookies() as unknown as UnsafeUnwrappedCookies).get('token')
```
#### `headers()`
`headers()` now returns `Promise<ReadonlyHeaders>`. Synchronous access
to the underlying Headers object is still supported to facilitate
migration.
```tsx
// ------------ preferred usage
// async Server Component
const header = (await headers()).get('x-foo')
// sync Server Component
import { use } from 'react'
//...
const header = use(headers()).get('x-foo')
// ------------ temporarily allowed usage
// javascript, dev warning at runtime
const header = headers().get('x-foo')
// typescript, dev warning at runtime
import { type UnsafeUnwrappedHeaders } from 'next/headers'
// ...
const header = (headers() as unknown as UnsafeUnwrappedHeaders).get('x-foo')
```
#### `draftMode()`
`draftMode()` now returns `Promise<DraftMode>`. Synchronous access to
the underlying DraftMode object is still supported to facilitate
migration.
```tsx
// ------------ preferred usage
// async Server Component
if ((await draftMode()).isEnabled) { ... }
// sync Server Component
import { use } from 'react'
//...
if (use(draftMode()).isEnabled) { ... }
// ------------ temporarily allowed usage
// javascript, dev warning at runtime
if (draftMode().isEnabled) { ... }
// typescript, dev warning at runtime
import { type UnsafeUnwrappedDraftMode} from 'next/headers'
// ...
if ((draftMode() as unknown as UnsafeUnwrappedDraftMode).isEnabled) { ... }
```
#### `searchParams`
`searchParams` is now a `Promise<{...}>`. Synchronous access to the
underlying search params is still supported to facilitate migration.
```tsx
// ------------ preferred usage
// async Page Component
export default async function Page({
searchParams
}: {
searchParams: Promise<{ foo: string }>
}) {
const fooSearchParam = (await searchParams).foo
}
// sync Page Component
import { use } from 'react'
export default function Page({
searchParams
}: {
searchParams: Promise<{ foo: string }>
}) {
const fooSearchParam = use(searchParams).foo
}
// ------------ temporarily allowed usage
// javascript, dev warning at runtime
export default async function Page({ searchParams}) {
const fooSearchParam = searchParams.foo
}
// typescript, dev warning at runtime
import { type UnsafeUnwrappedSearchParams } from 'next/server'
export default async function Page({
searchParams
}: {
searchParams: Promise<{ foo: string }>
}) {
const syncSearchParams = (searchParams as unknown as UnsafeUnwrappedSearchParams<typeof searchParams>)
const fooSearchParam = syncSearchParams.foo
}
```
#### `params`
`params` is now a `Promise<{...}>`. Synchronous access to the underlying
params is still supported to facilitate migration. It should be noted
that while params are not usually dynamic there are certain modes where
they can be such as fallback prerenders for PPR.
```tsx
// ------------ preferred usage
// async Segment Component
export default async function Layout({
params
}: {
params: Promise<{ foo: string }>
}) {
const fooParam = (await params).foo
}
// sync Segment Component
import { use } from 'react'
export default function Layout({
params
}: {
params: Promise<{ foo: string }>
}) {
const fooParam = use(params).foo
}
// ------------ temporarily allowed usage
// javascript, dev warning at runtime
export default async function Layout({ params}) {
const fooParam = params.foo
}
// typescript, dev warning at runtime
import { type UnsafeUnwrappedParams } from 'next/headers'
export default async function Layout({
params
}: {
params: Promise<{ foo: string }>
}) {
const syncParams = (params as unknown as UnsafeUnwrappedParams<typeof params>)
const fooSearchParam = syncParams.foo
}
```
### Typescript Changes
When using typescript with Next.js currently it is up to you to author
types for Pages, Layouts and other Segment components that recieve props
like `params` and `searchParams`.
Next comes with some build-time type checking to ensure you have not
improperly typed various top level module exports however the current
type assertions for `params` and `searchParams` is `any`. This isn't
very helpful because it allows you to erroneously type these props.
`searchParams` is tricky because while the default type is a dictionary
object parsed using node.js url parsing it is possible to customize when
running a custom Next.js server. However we can ensure that you
correctly type the prop as a Promise so with this change the validated
type for `searchParams` will be `Promise<any>`.
In the long run we will look at updating the `searchParams` underlying
type to be URLSearchParams so we can move away from supporting
customized parsing during rendering and we can get even more explicit
about valid types.
`params` is more straight forward because the framework controls the
actual `params` prop implementation and no customization is possible. In
the long run we want to enforce you are only typing params that are
valid for the Layout level your file is located in but for now we are
updating the allowed type to be `Promise<{[key: string]: string |
string[] | undefined }>`.
These new type restrictions may also require fixes before being able to
successfully build a project that updates to include these breaking
changes. These changes will also not always be codemodable because it is
valid to type the entire component using an opaque type like `Props`
which our codemods may not have an ability to introspect or modify.
|
||
|
|
f260328900 |
BREAKING CHANGE: Enable newNextLinkBehavior (#41459)
- Enable newNextLinkBehavior. See #36436 - Run next/link codemod on test suite Note that from when this lands on apps trying canary will need to run the new-link codemod in order to upgrade. Ideally we have to detect `<a>` while rendering the new link and warn for it. Co-authored-by: Steven <steven@ceriously.com> |
||
|
|
aed2dc0c85 |
Add handling for static generation in app (#40561)
x-ref: [internal notes](https://www.notion.so/vercel/App-Static-Generation-dc5f1e0916684501b586e56a5b7b9483) |
||
|
|
295f9da393 |
Client directive (#40415)
## Feature Change server components convention from using `.server.js` / `.client.js` file extension to determine it's a server or client component to using `'client'` js literal as a directive for determine client components boundary. React RFC: https://github.com/reactjs/rfcs/pull/189 New behavior doesn't consume `.server.js` as server components any more, if you're enabling `serverComponents` flag, every `page.js` in app dir will become server components by default. If you adding a `'client'` directive to the page, then that page will become a client component. This rule also applies to the normal js components, client components will require a `'client'` directive to indicate its identity, instead of having a `.client.js` extension. - [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` Co-authored-by: Shu Ding <3676859+shuding@users.noreply.github.com> |
||
|
|
71ad0dd0b0 |
Add prefetch to new router (#39866)
Follow-up to #37551 Implements prefetching for the new router. There are multiple behaviors related to prefetching so I've split them out for each case. The list below each case is what's prefetched: Reference: - Checkmark checked → it's implemented. - RSC Payload → Rendered server components. - Router state → Patch for the router history state. - Preloads for client component entry → This will be handled in a follow-up PR. - No `loading.js` static case → Will be handled in a follow-up PR. --- - `prefetch={true}` (default, same as current router, links in viewport are prefetched) - [x] Static all the way down the component tree - [x] RSC payload - [x] Router state - [ ] preloads for the client component entry - [x] Not static all the way down the component tree - [x] With `loading.js` - [x] RSC payload up until the loading below the common layout - [x] router state - [ ] preloads for the client component entry - [x] No `loading.js` (This case can be static files to make sure it’s fast) - [x] router state - [ ] preloads for the client component entry - `prefetch={false}` - [x] always do an optimistic navigation. We already have this implemented where it tries to figure out the router state based on the provided url. That result might be wrong but the router will automatically figure out that --- In the first implementation there is a distinction between `hard` and `soft` navigation. With the addition of prefetching you no longer have to add a `soft` prop to `next/link` in order to leverage the `soft` case. A heuristic has been added that automatically prefers `soft` navigation except when navigating between mismatching dynamic parameters. An example: - `app/[userOrTeam]/dashboard/page.js` and `app/[userOrTeam]/dashboard/settings/page.js` - `/tim/dashboard` → `/tim/dashboard/settings` = Soft navigation - `/tim/dashboard` → `/vercel/dashboard` = Hard navigation - `/vercel/dashboard` → `/vercel/dashboard/settings` = Soft navigation - `/vercel/dashboard/settings` -> `/tim/dashboard` = Hard navigation --- While adding these new heuristics some of the tests started failing and I found some state bugs in `router.reload()` which have been fixed. An example being when you push to `/dashboard` while on `/` in the same transition it would navigate to `/`, it also wouldn't push a new history entry. Both of these cases are now fixed: ``` React.startTransition(() => { router.push('/dashboard') router.reload() }) ``` --- While debugging the various changes I ended up debugging and manually diffing the cache and router state quite often and was looking at a way to automate this. `useReducer` is quite similar to Redux so I was wondering if Redux Devtools could be used in order to debug the various actions as it has diffing built-in. It took a bit of time to figure out the connection mechanism but in the end I figured out how to connect `useReducer`, a new hook `useReducerWithReduxDevtools` has been added, we'll probably want to put this behind a compile-time flag when the new router is marked stable but until then it's useful to have it enabled by default (only when you have Redux Devtools installed ofcourse). > ⚠️ Redux Devtools is only connected to take incoming actions / state. Time travel and other features are not supported because the state sent to the devtools is normalized to allow diffing the maps, you can't move backward based on that state so applying the state is not connected. Example of the integration: <img width="1912" alt="Screen Shot 2022-09-02 at 10 00 40" src="https://user-images.githubusercontent.com/6324199/188637303-ad8d6a81-15e5-4b65-875b-1c4f93df4e44.png"> |
||
|
|
f05cee2d85 | Rename query to searchParams for getServerSideProps in new Router (#38654) | ||
|
|
f7de771beb |
Migrate rsc integration tests to e2e tests (#38460)
* Migrate rsc integration tests to e2e tests * skip image test * remove image page Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> |