vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.87k stars 26.97k forks source link

[NEXT-1186] revalidatePath not working for dynamic routes while res.revalidate works fine #49387

Closed roxizhauk closed 1 year ago

roxizhauk commented 1 year ago

Verify canary release

Provide environment information

    Operating System:
      Platform: darwin
      Arch: x64
      Version: Darwin Kernel Version 22.5.0: Mon Apr 24 20:51:50 PDT 2023; root:xnu-8796.121.2~5/RELEASE_X86_64
    Binaries:
      Node: 16.17.1
      npm: 8.19.2
      Yarn: N/A
      pnpm: N/A
    Relevant packages:
      next: 13.4.7-canary.1
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.1.3

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/roxizhauk/revalidate-path CodeSandBox

To Reproduce

Visit /blog/[anything] (e.g. /blog/test ) to generate a dynamic page and check the time shown

To try "unsuccessful" app directory's revalidatePath:

  1. hit /api/revalidate-path?path=/blog/[anything] and you'll see "revalidated"
  2. refresh /blog/[anything] and you'll see the time not changed

To try "successful" pages directory's res.revalidate:

  1. hit /api/revalidate?path=/blog/[anything] and you'll see "revalidated"
  2. refresh /blog/[anything] and you'll see the time changed

Describe the Bug

The app directory's revalidatePath works fine for "/" or "/blog" but dynamic routes like "/blog/1" or "/blog/test" while pages directory's res.revalidate works fine for all

Expected Behavior

revalidatePath works the same as res.revalidate for dynamic routes

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

From SyncLinear.com | NEXT-1186

roxizhauk commented 1 year ago

https://github.com/vercel/next.js/issues/49778#issuecomment-1546968234

ijjk commented 1 year ago

@roxizhauk from your reproduction steps it seems you are not calling revalidatePath() with the correct pathname as it needs to math the name of the page on your filesystem e.g. app/blog/[id]/page it should be revalidatePath(/blog/[id]) not revalidatePath(/blog/first)

Related response here https://github.com/vercel/next.js/issues/49778#issuecomment-1547028830

ijjk commented 1 year ago

Note: we are investigating this behavior more and will add updates here

danteissaias commented 1 year ago

@roxizhauk from your reproduction steps it seems you are not calling revalidatePath() with the correct pathname as it needs to math the name of the page on your filesystem e.g. app/blog/[id]/page it should be revalidatePath(/blog/[id]) not revalidatePath(/blog/first)

Related response here #49778 (comment)

There's no way to trigger a revalidation of a specific dynamic route?

ijjk commented 1 year ago

At the moment res.revalidate() can achieve this for ISR paths, we are investigating this behavior for revalidatePath() though.

eric-burel commented 1 year ago

Hi, I ended up here while looking for a way to provoke hard navigate:

So I changed the router.push to window.location.href to provoke a refresh but that's not very good. I might instead create an endpoint to get fresh data from the client but that kinda defeats the purpose of the RSC.

Is this what you tried to achieve @roxizhauk?

All those revalidate method seems to target static/ISR, to invalidate the server cache, but I struggle to find a solution to provoke hard navigates client-side after an update, telling to router.push("/response/1234") that response "1234" has been modified since the page was loaded.

roxizhauk commented 1 year ago

@eric-burel I don't think it is. What I'm trying to do here is on-demand ISR for dynamically generated static pages.

I just quickly built a simple mock with your routing and timing function that I used for my repo but it properly changes the time both on layout and pages. It's weird that your layout isn't updated even with export const dynamic = "force-dynamic" I think you should post the issue with a repo.

kavehsaket commented 1 year ago

Same issue with revalidate path. The path I'm trying to revalidate is like ==> [ws]/blogs/[id]. Also, if I use GET for revalidate I get 405 and If I use POST, it won't be any error. However, in your docs, you suggest using GET for on-demand re-validation. https://nextjs.org/docs/app/api-reference/functions/revalidatePath

tutorialcode commented 1 year ago

@ijjk Hi, is the revalidatePath function designed to be revalidating an individual path or the route?

matthewwilson commented 1 year ago

We are experiencing the same issue. Using the examples above:

App Router

revalidatePath("/blog/[id]") -> works locally (pnpm build && pnpm start), but does not work on Vercel preview or production deployments.

revalidatePath("/blog/first") -> does not work

Pages Router

await res.revalidate("/blog/first"); -> works locally and on Vercel

hauptrolle commented 1 year ago

Same problem here. Event though the revalidation is not working for /en/foo/bar/ nor [lang]/[[...slug]]/

levipadre commented 1 year ago

How should I revalidation from the url if my entire site's route is dynamic? app/[lang]/[[...slug]]/page I thought maybe https://domain.com/api/revalidate?path=/foo-bar but it doesn't revalidate. I have the revalidation code in pages, but I tried in app, but none worked.

I also have a middleware, as I have different domain names, like domain.it and domain.fr etc.

My code:

export async function getPageByUrl(slug: string[]) {
    try {
        const path = (typeof slug === 'undefined' || slug[0] === 'index') ? '' : slug.join('/');
        const res = await fetch(`https://domain.com/api/posts?url=/${path}`)
        return await res.json();
    } catch (error) {
        console.log(error);
        return { notFound: true };
    }
}

export async function generateStaticParams() {
    return [];
}

export default async function Page({
    params
}: {
    params: {
        slug: string[]
    }
}) {
    const slug = params.slug;
    const [PAGE] = await Promise.all([getPageByUrl(slug)]);

    return (
        <div className='container'>
            <div dangerouslySetInnerHTML={{__html:PAGE[0].body}}></div>
        </div>
    )
}
sdjnes commented 1 year ago

@levipadre The way I handled this was with a tag in the fetch with the slug of the page.

With your fetch tagged, you can pass that as the url parameter to the revalidate API call.


I worked with another dev on the project to discover this, so if you're interested in why it works this way:

Firstly, it's worth noting that revalidatePath actually just calls revalidateTag (file here). So rather than differentiating between them, let's stick with tag for everything.

Next is caching your fetch responses on the filesystem inside .next/cache/fetch-cache. There are a bunch of generated JSON files which are the fetch responses themselves, and then a manifest which maps a tag to a JSON filename

Checking this manifest, you'll see that it's not actually caching against the slugs of your pages, but against the filesystem path (the one with dynamic segements in it). As an example, here's an excerpt from mine:

"/[locale]/[[...slug]]/page": {
      "keys": [
        "97579d80f5605b49cad029b3856c33395a948fe95d061a2209d11bc4eedd98ee",
        "df56bce481a809756e395b558faf3f902327952b220f7c5b39b16c86518bb769"
      ]
    },

Calling revalidate against a specific slug (like /my-page) won't revalidate anything because that key isn't in the manifest.

You can see there are various keys under a dynamic segment route, and these relate to how many slugs have been hit for that route. E.g. my first key is for /en/my-page and the second for /en/my-second-page.

I could revalidate /[locale]/[[...slug]]/page, but then every single one of my pages' fetch calls will be revalidated, which isn't ideal when you just want to do one page. That's why adding a custom tag and revalidating against that worked well for us.

levipadre commented 1 year ago

Thank you for the explanation @sdjnes. I did check the manifest in fetch-cache and I saw two tags: "tags":["/[lang]/[[...slug]]/page","/[[...slug]]/page"], so I tried domain.com/api/revalidate?tag=/[lang]/[[...slug]]/page which didn't revalidate, but /api/revalidate?tag=/[[...slug]]/page did revalidate.

So yes, I assume it revalidated all pages in this case. I have a few thousand pages generated, so obviously, I don't want to revalidate all of them.

The only thig that I don't understand, that if I add a tag to my fetch, like this:

export async function getPageByUrl(slug: string[]) {
    try {
        const path = (typeof slug === 'undefined' || slug[0] === 'index') ? '' : slug.join('/');
        const res = await fetch(`https://domain.com/api/posts?url=/${path}`, {
            next: {
                tags: ['posts'],
            }
        })
        return await res.json();
    } catch (error) {
        console.log(error);
        return { notFound: true };
    }
}

and I call domain.com/api/revalidate?tag=posts, it's still going to revalidate all the pages, right?

What I would like to do is to revalidate only one page. There is a headless CMS, and all the data comes from a restAPI. When an editor modifies a page (e.g. https://domain.com or https://domain.com/foo or https://domain.com/foo/bar etc.), I would like to update the content on this page. Same situation when someone creates a new page or deletes one.

Is there any way to do that? Maybe dynamically add tags to all pages based on their path?

sdjnes commented 1 year ago

You can use the slug as the tag. This is how ours looks:

const slugString = `/${slug.join('/')}`;

...
    next: {
      tags: [
        slugString === '/' ? '/homepage' : slugString,
        ]
    }
...

The edge-case for the homepage is because calling revalidateTag('/') will revalidate everything in the cache, which is obviously not what we want to do.

Then, in our CMS, we have an onUpdate hook that calls the frontend with the slug of the page (with the logic to handle the homepage edge case) -- something like /api/revalidate?tag=/my-cms-page

levipadre commented 1 year ago

Wow, thank you so much @sdjnes . You are a lifesaver! It works like a charm.

You have already helped a lot, but if you don't mind, I would like to ask one more thing. Also cache related, and it seems you know your way around this topic.

My menu is acting weird cache-wise. The menu also generated by json and however I added a tag to it, it just doesn't want to cache it. Whenever I change the menu data, it refreshes immediately.

The menu is in a header, which is a client component. My code look like this:

export async function getMenu() {
    try {
        const res = await fetch(`https://domain.com/api/menu`, {
            next: {
                tags: ['menu'],
            }
        })
        return await res.json();
    } catch (error) {
        console.log(error);
        return { notFound: true };
    }
}

the header

"use client";

import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import Link from 'next/link';

import React, { useEffect, useState } from 'react';
import { getMenu } from '@/lib/APIs/menu';
import { use } from 'react';

const dataPromise = getMenu();

const Header = () => {
    const MENU = use(dataPromise);

    return (
        // use ClientOnly to prevent hydration mismatch
        <ClientOnly>
            <header>
                <Navbar>
                    <Container>
                        <Link href='/'>Logo</Link>
                        <Navbar.Collapse>
                            <Nav>
                                {MENU.map((item: any) => {
                                    return (
                                        <Nav.Item key={item.id}>
                                            <Link href={item.url} prefetch={false}>{item.title}</Link>
                                        </Nav.Item>
                                    );
                                })}
                            </Nav>
                        </Navbar.Collapse>
                    </Container>
                </Navbar>
            </header>
        </ClientOnly>
    );
}

export default Header;

export default function ClientOnly({ children }: { children: React.ReactNode }) {
    const [canRender, setCanRender] = useState(false);
    useEffect(() => {
        setCanRender(true);
    }, []);

    if (!canRender) return null;

    return <>{children}</>;
}

and I just import to the layout.

Can I cache the same way as the posts?

lukepearce commented 1 year ago

@levipadre - the above is related to this issue. If you need help with something else there are loads of other places to do so. This issue's comments should be kept on topic to avoid confusion and therefore extra effort for the maintainers.

levipadre commented 1 year ago

Yes, it's true, I understand. I took a shot. Thanks anyway

levipadre commented 1 year ago

Doing some further testing, I noticed an issue. I don't know if it's only my code or generic, but when I revalidate /foo page, it also revalidates /foo/bar page too. By checking .next/cache/fetch-cache, I saw that every tag does contain my generated tags, but it also contains a tag too (not by me), which is the same in every file. E.g.:

"tags":["/foo","/[[...slug]]/page"]

and

"tags":["/foo/bar","/[[...slug]]/page"]

...so I assume because of the common /[[...slug]]/page"] tag whenever I call a revalidation it revalidated every pages.

SSardorf commented 1 year ago

This is actually pretty critical - Essentially the previous res.revalidate supported this functionality.

To allow for dynamic revalidation, I tried to use a /pages/api endpoint to revalidate a dynamic path located in my app directory, however, this also fails because (https://github.com/vercel/next.js/issues/48771) res.revalidate requires getStaticProps, which the app directory does not use anymore.

This means that effectively there's no way to revalidate dynamic paths in the app directory. You can use the tag revalidation, but that is only compatible with fetch. Use cases where data is fetched directly from a database can therefore not make use of on-demand revalidation for dynamic paths.

This means that we have two options: a) Move all routes to the pages directory or b) Use a sub-optimal revalidation strategy where you (e.g.) invalidate ALL blogpost [ids] versus just id 1 when id 1 changes.

Both seem sub-optimal

SSardorf commented 1 year ago

Is there any possibility of adding tag as a const in the page functions. So instead of cache tags only working in fetch requests, it could work for any type of data fetching (E.g. using Prisma)

Example if we have app/[id]/page.tsx

export default async function Post(params:{id:string}){
   const TAG = params.id
   const post = await prisma.post.findUnique({
     where: {
        id: params.id
      }
}

That way, we could invalidate whichever data fetching strategy is being used (be it fetch or directly from database or whichever)

jwalcher commented 1 year ago

for me, res.revalidate still works on dynamic routes created in the app directory, but not if middleware gets in the way, as described in https://github.com/vercel/next.js/issues/50464 I'm also not using standard fetch requests, so your tagging proposal looks critical indeed @SSardorf

SSardorf commented 1 year ago

Okay - Seems like we can use unstable_cache to achieve the same functionality as fetch tag invalidation, but for any type of data fetching.

import { unstable_cache } from 'next/cache'

export default async function post({
  postId
}: {
  postId:string
}) {
const posts = await unstable_cache(
    async () => {
      return prisma.post.findUnique({
        where: {
         postId: postId,
        }
      })
    },
    [postId],
    { tags: [postId] }
  )()

Then you can invalidate the post by invalidating the postId as a tag like so: revalidateTag(postId)

swarnava commented 1 year ago

Hi,

We have recently pushed couple of ISR related fix in Next.js version 13.4.5. Could you try and let us know how it goes?

jwalcher commented 1 year ago

@swarnava no change for me, same issues as before

levipadre commented 1 year ago

As I mentioned before, revalidation is working for me (App Router API), but not how it should. It revalidates not only the given tag (path in my case) but also every child's path as well. https://github.com/vercel/next.js/issues/49387#issuecomment-1583353332 Would it be a crazy idea not to add anything to tags by default so that the developer could decide entirely? If I want to revalidate "/foo" I add foo and not worry about "/foo/bar" etc. because my structure "/[[...slug]]/page" is included by default. If someone wants to revalidate every page, they can still add "/[[...slug]]/page" as a tag.

bregtemundo commented 1 year ago

We have a website coupled to a headless cms and almost all of our pages are in a catchall route. app/[[...slug]]. Some are static prebuild with generateStaticParams. With revalidatePath it looks like I can only revalidate /[[...slug]] , which would basically invalidate almost every page in the website. This is working, however it seems to me if we could invalidate a single page this would be more efficient?

vikvvi commented 1 year ago

We use App directory, and as previous commenter, we also use [[...slug]] for all pages, and need re-validate just some pages by path. Any solutions?

roxizhauk commented 1 year ago

Seemingly reavalidatePath has yet to function as we expected, even in the latest version 13.4.7-canary.1. Should I update the repo with it?

karlhorky commented 1 year ago

@roxizhauk I would say yes - always good to have an updated reproduction with the latest version.

If you could make a demo on CodeSandbox or StackBlitz this can also make it easier for others to set up the environment to see the problem, confirm the behavior and try out fixes

Galanthus commented 1 year ago

Trying for 7 days to make this revalidatePath API route work. Everything works perfectly fine, when fetching the data from a CMS like Sanity. When publishing, saving changes on a dynamic page or blog post I get to see status code 200 and and the updated values from the console log and log file itself. However, when I refresh a page or a post I don't see any changes at all. I am not using a CDN have tested my env variables, logged all possible constants nothing is undefined or empty. But no luck.. I have to manually re-build my app in order to see the changes.

Same problem for me... Webhook is triggered but content and pages are not updated.

indeox commented 1 year ago

@Galanthus Out of curiosity, on Vercel, are you trying this on the main branch, or a Preview branch?

(only asking cos I was struggling with it too, but managed to narrow it down to the Preview environments - it works on the Production one)

Galanthus commented 1 year ago

@indeox Thank you for the reply. I have manually deployed my repository from my vscode terminal to production on Vercel. Previewing documents when editing pages, posts work just perfectly fine. console logging all the constants, env variables non is emtpy or undefined. When testing locally with a webhook URL to my NGROK/Expose and console log the data I get to see a status code of 200, the updated content in JSON format but refreshing that specific page doesn't do anything. GenerateStaticParams Is not triggered again when updating and firing the hook.

indeox commented 1 year ago

@Galanthus Ah right, maybe something else then.

However as far as I know, generateStaticParams() does not get triggered again when revalidatePath is called. It just invalidates the cache and then only rebuilds the page upon request.

Galanthus commented 1 year ago

@indeox After refactoring my API and multiple code parts for days. I have finally figured it out. Since Next 13 there is no option to set fallback: true like we would do in previous version. Instead you have to to set export const dynamic = "auto" to each dynamic route in order to make it work after saving/publishing a post or page in order to re-generate the static file.

tutorialcode commented 1 year ago

@indeox After refactoring my API and multiple code parts for days. I have finally figured it out. Since Next 13 there is no option to set fallback: true like we would do in previous version. Instead you have to to set export const dynamic = "auto" to each dynamic route in order to make it work after saving/publishing a post or page in order to re-generate the static file.

Auto is the default option though, I don't think you have to set it explicitly. Can you elaborate what the problem was if you didn't set dynamic to auto?

Galanthus commented 1 year ago

@indeox After refactoring my API and multiple code parts for days. I have finally figured it out. Since Next 13 there is no option to set fallback: true like we would do in previous version. Instead you have to to set export const dynamic = "auto" to each dynamic route in order to make it work after saving/publishing a post or page in order to re-generate the static file.

Auto is the default option though, I don't think you have to set it explicitly. Can you elaborate what the problem was if you didn't set dynamic to auto?

That's what I thought removing the auto stops the revalidate function from working. It won't re-generate my static file.

The problem is that revalidatePath didn't work at all before including the dynamic option even if by default enabled.

Just removed to test if it works with export const dynamic = "auto" and result is my page is not re-created.

indeox commented 1 year ago

@indeox After refactoring my API and multiple code parts for days. I have finally figured it out. Since Next 13 there is no option to set fallback: true like we would do in previous version. Instead you have to to set export const dynamic = "auto" to each dynamic route in order to make it work after saving/publishing a post or page in order to re-generate the static file.

Auto is the default option though, I don't think you have to set it explicitly. Can you elaborate what the problem was if you didn't set dynamic to auto?

Yes, that's what I thought as well.

In my case I just set export const revalidate = 3600; (1 hour TTL), and then just call revalidatePath whenever there's a change from the CMS.

Galanthus commented 1 year ago

Yes, that's what I thought as well.

In my case I just set export const revalidate = 3600; (1 hour TTL), and then just call revalidatePath whenever there's a change from the CMS.

In my case setting export const revalidate = 3600 and removing the dynamic auto won't work anymore. Really not sure why this is the case for my code.

import { draftMode } from "next/headers"
import { notFound } from "next/navigation"

import { getAllPosts, getPage, getPageMeta, token } from "@/lib/sanity/sanity.helpers"
import { postSingleQuery } from "@/lib/queries"

import { PreviewExit } from "@/components/previews"
import { PostPageLayout } from "@/components/layout"

import { Post } from "@/models/documents/post"
import { PreviewProvider } from "@/context"

export const generateMetadata = async ({ params }: { params: { slug: string } }) => {
  const page = await getPageMeta({
    type: "post",
    slug: params.slug
  })

  return page
}

export const generateStaticParams = async () => {
  const posts = await getAllPosts<Post[]>("post")

  return posts.map((post) => ({
    slug: post.slug.current
  }))
}

// export const dynamic = "auto"

// Revalidate every hour or revalidate on request.
export const revalidate = 3600

const BlogPost = async ({ params }: { params: { slug: string } }) => {
  const preview = draftMode().isEnabled ? { token } : undefined

  const post = await getPage<Post>(postSingleQuery, params.slug)

  if (!post) {
    notFound()
  }

  if (preview) {
    return (
      <PreviewProvider token={preview.token!}>
        <PostPageLayout post={post} slug={params.slug} />
        <PreviewExit />
      </PreviewProvider>
    )
  }

  return <PostPageLayout post={post} />
}

export default BlogPost
jwalcher commented 1 year ago

Sorry if I am misunderstanding, but are you now calling revalidatePath on dynamically generated routes /posts/1, /posts/2, etc. or do we still need to put the path /posts/[id]

Also, a general question about ISR: How do people deal with deleting specific routes when the content is deleted from CMS?

lukepearce commented 1 year ago

Sorry if I am misunderstanding, but are you now calling revalidatePath on dynamically generated routes /posts/1, /posts/2, etc. or do we still need to put the path /posts/[id]

Also, a general question about ISR: How do people deal with deleting specific routes when the content is deleted from CMS?

Ask general questions somewhere like the community Discord - not this Issue which is about a specific topic.

jwalcher commented 1 year ago

@lukepearce Sorry, this is not general question of someone looking for help, but a specific inquiry about which revalidation strategy is being pursued in this thread. I've been following this for some time now, and anxious for revalidation methods to work according to specs, which is precisely what this issue is about, or?

markomitranic commented 1 year ago

I can confirm that I've lost brain cells on this as well. Recap of the above posts in this thread is that right now is that there is no way to bust cache for a specific page.

If one is brave or desparate and still choses to do the tagging, I would say it is in fact the most correct way of doing this as it is basically what revalidatePath is doing as well. Unfortunately it is not a stabile API, it is entirely undocumented, not pretty to look at, and very implicit in usage, and finally, it doesn't just invalidate the tag - it caches the data, meaning it performs data transformations, and there are other GithHub issues open about those being inaccurate. 😭

For reference, for future readers, attaching an example of how I do that currently:

export default async function Post({ params }: { params: { slug: string } }) {
  const post = await unstable_cache(
    async () => await Posts.get(params.slug), [], {
        tags: [`Posts.get-${params.slug}`]
    })();
  return <>Hello {post.name}</>;
}

// Elsewhere
revalidateTag(`Posts.get-${params.slug}`);
revalidateTag(`Posts.all`);

So, there effectively is no way to exert control over cache invalidation. Which was supposed to be the strongest point of the App router performance...

jwalcher commented 1 year ago

Thanks for the excellent summary! The lack of documentation of these issue is a bit surprising. Like, if I did not have a working revalidation with the old method, I would probably not get it running now. Will give the tags a try though.

markomitranic commented 1 year ago

@jwalcher Absolutely agree. It is kind of weird that Next, a framework known for championing caching stuff into the mainstream, released a version that just glosses over the whole caching thing, in a release that was supposed to be all about caching :D

How is it working for you right now? Via pages router? Can you tell us a bit more please, I would find it very useful, as am sure will others.

I guess you are using the pages router??

export default async function handler(
    _req: NextApiRequest,
    res: NextApiResponse,
) {
    await res.revalidate(`/test`);
    await res.revalidate(`/test/slug`);
    return res.json({ revalidated: true });
}
jwalcher commented 1 year ago

Yes, I have a route handler in the pages directory calling res.revalidate on individual routes which themselves are in the app directory. I even had a workaround to revalidate also in dev mode by simply clearing the cache altogether

pages/api/revalidate.ts

const directory = process.cwd() + "/.next/cache/fetch-cache";

export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
    if (process.env.NODE_ENV === "development") {
      console.log("clearing cache");
      fs.readdir(directory, (err, files) => {
        for (const file of files) {
          fs.unlink(path.join(directory, file), (err) => { if (err) throw err; });
        }
      });
      return res.json({ cacheCleared: true });
    } else {
      const pathsToRevalidate = revalPaths(req.body);
      console.log("revalidating", pathsToRevalidate);
      // The argument of res.revalidate should be the path of the actual route not a rewritten or
      // dynamically generated one, e.g. for "/blog/[slug]" this should be "/blog/post-1"
      await Promise.all(pathsToRevalidate.map((path) => res.revalidate(path)));
      return res.json({ revalidated: true });
    }
}

This strategy was working fine until v13.3, at which point some changes introduced in the Next server led to interference with my middleware, see https://github.com/vercel/next.js/issues/50464 Now I could work around the middleware, but my hack to revalidate in dev mode definitely does not work anymore starting in v13.5 or so. This all sounds convoluted, and it is, but my feeling is that the issues (with the old method) would be fairly easy to resolve if somebody cared to look. Alternatively, I'd be happy to wait if there was a clear statement about what the plans are to finally address the issues that @markomitranic you so well summarized.

edjust commented 1 year ago

Operating System: Platform: darwin Arch: x64 Version: Darwin Kernel Version 21.6.0: Thu Sep 29 20:12:57 PDT 2022; root:xnu-8020.240.7~1/RELEASE_X86_64 Binaries: Node: 18.13.0 npm: 8.19.3 Yarn: 1.22.10 pnpm: 8.3.1 Relevant packages: next: 13.3.0 eslint-config-next: 13.3.0 react: 18.2.0 react-dom: 18.2.0

My Next.js project is not functioning properly in the production environment (Firebase Hosting). When I run the build, export, and start commands locally on my machine, everything works fine. However, I'm encountering issues with the revalidate and fallback features not working as expected in the production environment.

I have already updated my Next.js version to 13.3.0 based on this discussion https://github.com/vercel/next.js/discussions/42290#discussioncomment-5502200, but the issue persists.

I expect the revalidate and fallback features to work correctly in the production environment as they do on my local machine.

Actual Behavior: In the production environment, the revalidate and fallback features are not functioning as expected. The pages do not update automatically with the specified revalidate interval, and the fallback behavior is not working with new params that wasn't specified initially.

My page is /profile/[id].tsx

const Profile: NextPage<ProfileProps> = ({ profile }) => {
  const { isFallback } = useRouter()

  if (isFallback) {
    return <Loading />
  }

  return (
   ...
  )
}

export const getStaticPaths: GetStaticPaths = async () => {
  const urlIds = ['1']
  const paths = urlIds.map((urlId) => ({
    params: { id: urlId }
  }))

  return { paths, fallback: true }
}

export const getStaticProps: GetStaticProps<ProfileProps> = async ({ params }) => {
  const urlId = params?.id

  const response = await api.get(`profiles/${urlId}`)
  const profile: Profile | null = response.data?.profile || null

  return { props: { profile }, revalidate: 30 }
}

export default Profile
leerob commented 1 year ago

The lack of documentation of these issue is a bit surprising.

@jwalcher to be clear, you are referring to an unstable API that will change and should not yet be adopted. Unstable features are not published to the documentation for this reason.

@edjust please open a separate issue with a reproduction 🙏

It is kind of weird that Next, a framework known for championing caching stuff into the mainstream, released a version that just glosses over the whole caching thing, in a release that was supposed to be all about caching

I don't know if I'd say we "glossed over it" – it's documented! But we are working to make the documentation better and add diagrams to help explains concepts. That should be landing shortly https://github.com/vercel/next.js/pull/52514.

markomitranic commented 1 year ago

@leerob oh, I wasn't aware that it was unstable, that changes things a lot and I'm sorry for saying "glossed over". I was under the impression that both Route Handlers and revalidate are stable. Probably as there is no warning on their docs saying they are unstable :/

In any case, I don't think there is a misunderstanding of diagrams here, its purely that a major piece of old functionality is missing entirely.

jwalcher commented 1 year ago

@leerob to follow up on @markomitranic -- the issue for me is that the development of the (as you say) unstable revalidatePath/revalidateTag API has broken the old res.revalidate() (at least for me). So I'm a bit stuck between a rock and a hard place here. Otherwise happy to wait!