Commit Graph

12 Commits

Author SHA1 Message Date
JJ Kasper 7e325d98a2 Fix tags check for expired/stale (#84717)
Corrects the expired/stale checks in the tags manifest and updates test
case to capture this.
2025-10-09 17:03:39 -07:00
JJ Kasper e598a4f976 Add new updateTag API and new signature for revalidateTag (#83822) 2025-10-09 14:03:01 -07:00
JJ Kasper a771ce2ff2 Undeprecate revalidate APIs and rename expire APIs (#73193)
As discussed we are removing the deprecated field from `revalidateTag`
and `revalidatePath` as they aren't going away anytime soon and the new
`expire` APIs will have slightly different semantics so not a 1-1 rename
with no arguments change anymore. We are also adding the `unstable_`
prefix for `expireTag`/`expirePath` while we iterate on them so it's
clear they will change.

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
2024-11-27 09:01:04 -08:00
JJ Kasper 0bc4086dff Add expireTag and expirePath APIs (#72485)
This adds `expireTag()` and `expirePath` APIs which are replacing
`revalidateTag` and `revalidatePath`. As such this also marks
`revalidatePath` and `revalidateTag` as deprecated and marked for
removal.

The arguments passed to these methods match `revalidateTag` and
`revalidatePath` with the exception that `expireTag` can receive
multiple tags now as separate arguments which matches our new
`unstable_cacheTag()` API.

We are wanting to update these APIs as `revalidateTag` naming has caused
some confusion as to when the affected items will be updated. The
underlying behavior of `expireTag` is to mark an entry as out of date
and for it to no longer be used so we are expiring it.

If you call `expirePath('/blog/first'`) the next time that item is
requested it is considered expired so the cache will not be used for it
and a fresh entry will be generated. Similarly for `expireTag('posts',
'blog')` the next time any cache with that tag is requested it's
respective cache will not be leveraged and new cache will be populated.

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Co-authored-by: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com>
2024-11-11 19:23:39 +00:00
Josh Story 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.
2024-09-25 15:44:37 -07:00
JJ Kasper 6f9c4720f9 [Breaking] Disable automatic static generation for route handlers (#65825)
## Background

Currently route handlers will be attempted to statically generate the
same as normal pages in app router. If a route handler accessed dynamic
data such as `cookies()`, `headers()`, `req.url`, or similar we would
bail from this static generation but this didn't handle the case where
you would do data fetching via a non-fetch based library e.g. `redis` or
even using `Date.now()` or `Math.random()` so users would expect these
to be run every request like normal API endpoints but they would be
static so wouldn't be executed again after a build.


## New Behavior

As discussed this disable the default static generation handling for
route handlers as we've seen this confuse our users since in most cases
when creating a route handler you are handling dynamic workloads. This
doesn't remove the ability to static generate route handlers though as
you can still manually specify:

If you specify `export const revalidate = 1` we will still bail if
dynamic data is accessed to prevent accidental `revalidate` configuring
but this can be avoided via `force-static` being used as well.
 
```js
export const dynamic = 'force-static'
// or
export const dynamic = 'error'
// or
export const revalidate = false
// or
export const revalidate = 1 // value great than 0
// or 
export const generateStaticParams() {}
```

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
2024-05-20 16:25:02 -07:00
Alexander Savelyev 2ebc58cca3 add tests for incremental-cache (#60331)
### What?
In the previous PR (#60249), it was found that the case when the
response size is larger than 2MB is not sufficiently covered by tests
[[comment](https://github.com/vercel/next.js/pull/60249#pullrequestreview-1807140518)]

### How?
I added a few more checks.

Just checking the number of API calls is not enough - I believe it is
important to additionally verify what exactly the page received.

If I'm honest, I couldn't find the exact reason why data is loaded after
application start when using `customCacheHandler`, but without it -
during build. It seems like something in the butcher. Therefore, in this
part, I simply configured it for the current version.

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2024-01-09 11:07:08 -08:00
Alexander Savelyev 269020a028 Disable 2mb limit for custom incrementalCacheHandler (#59976)
### Fixing a bug

### What?

Disable 2MB limit for custom incrementalCacheHandler

### Why?

The limit is necessary because `FetchCache` has a 2MB limit, but it
seems there was a miscommunication regarding the key coincidence, where
`fetchCache` is a flag indicating that the method is called from fetch,
rather than indicating that the `FetchCache` Provider is currently being
used.

We do not use Vercel, and as I understand it, we do not have the
opportunity to use this functionality.

In any case, it is more important for us to increase the limits, and in
some cases, using a file storage is even preferable.

### How?

I have created a flag that determines whether the use of `FetchCache` is
possible at least in theory - if no custom provider is passed, and
additionally configured it so that it is not an implementation of
`FetchCache` as a protection against special individuals (*like me :)*).

If everything is fine, I will write proper tests.

Also, I would like to recommend making `FileSystemCache` public (_i.e.
support it as public functionality_) so that it can be imported and
extended or simply used to fix only it.

Fixes #48324 (partially)

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2024-01-04 15:12:43 -08:00
JJ Kasper f749684921 Update cache handling in draft mode (#51663)
Similar to `getStaticProps` when draft mode is enabled we should bypass
the cache so fresh data is shown.

x-ref: [slack
thread](https://vercel.slack.com/archives/C052BQ8F9EJ/p1687440125828479?thread_ts=1684336597.437409&cid=C052BQ8F9EJ)
and
[tweet](https://twitter.com/tomus_sherman/status/1669023879656873988)

---------

Co-authored-by: Steven <steven@ceriously.com>
2023-06-22 14:03:11 -07:00
JJ Kasper e4beb85b49 Update cache methods for app dir (#49165)
This ensures we match behavior local and deployed and also cleans up
naming of the methods.

---------
2023-05-03 13:21:58 -07:00
JJ Kasper abc74fb92e Update revalidate handling for app (#49062)
This updates revalidate handling for app and moves some exports from
`next/server` to `next/cache`

x-ref: [slack
thread](https://vercel.slack.com/archives/C042LHPJ1NX/p1682535961644979?thread_ts=1682368724.384619&cid=C042LHPJ1NX)

---------
2023-05-02 08:19:02 -07:00
JJ Kasper 864f065392 Add updated app dir cache handling (#48516)
This adds the discussed updated cache handling for app dir for more fine
grained control over the cached values. Also adds initial
`revalidateTag` and `revalidatePath` exports from `next/server` although
export location may change.

Continuation of https://github.com/vercel/next.js/pull/47720

x-ref: [slack
thread](https://vercel.slack.com/archives/C042LHPJ1NX/p1681404363048459)
2023-04-19 18:12:21 -04:00