Prisma 和 Next.js:内容在重新部署之前不会更新。

huangapple go评论68阅读模式
英文:

Prisma and Nextjs: content is not updating until re-deploy

问题

以下是您要翻译的内容:

这是我刚在Vercel上部署的网站。我正在使用Prisma和Next.js构建一个Web应用程序,我遇到了一个问题,即内容在我手动重新部署应用程序之前不会实时更新。以下是情况描述:

  • 我在Next.js应用程序中有一个API端点,该端点从Prisma数据库中获取数据。
  • 当我通过应用程序创建或更新数据库中的数据时,这些更改会立即反映在开发环境中,但在生产环境中不会反映,直到我重新部署应用程序。

这是我在前端获取数据的方式:

const { data: posts, error } = useSWR(`/api/getPosts`, fetcher, {refreshInterval:1000});

这是用于发布内容的API端点:

// 将帖子添加到Prisma后端

import { NextResponse, NextRequest } from 'next/server';
import prisma from '../../../prisma/client';

// 函数

export async function POST(request:NextRequest) {
    const data = await request.json();
    const title = data.title;
    const user = await prisma.user.findUnique({
        where: {
            email : data.email
        }
    })
    if (!user){
        // 返回错误
        return NextResponse.json({error: "用户未找到"}, {status: 404})
    }

    if (!title){
        // 抛出错误
        return NextResponse.json({error: "标题是必需的"}, {status: 400})
    }

    if (title.length > 300){
        return NextResponse.json({error:"标题不得超过300个字符"}, {status:400});
    }
    const userId = user?.id;

    const post = await prisma.post.create({
        data: {
            title,
            userId
        }
    })
    try{
        return NextResponse.json({post},{status:200})
    }catch(error){
        return NextResponse.json({error}, {status:500})
    }
}

获取所有帖子的API端点:

import { NextRequest, NextResponse } from 'next/server'
import prisma from '../../../prisma/client'
import { NextApiResponse } from 'next';


export async function GET(request:NextRequest){
    const posts = await prisma.Post.findMany({
        include: {
            user: true
        },
        orderBy:{
            createdAt: 'desc'
        }
    })
    try{
        // 返回所有帖子
        return NextResponse.json({posts},{status:200})
    }catch(error){
        return NextResponse.json(error, {status:500});
    }
}

如何确保内容更新立即在生产环境中反映,而无需手动重新部署?

这是GitHub仓库的链接

更新

我能够发出POST请求并对数据库进行更改,我认为问题应该与GET请求有关,因为数据似乎在我刷新页面时仍然是静态的。

这是我在Vercel上的运行时日志:

Prisma 和 Next.js:内容在重新部署之前不会更新。

英文:

Here is the website that I just deployed on Vercel. I am building a web application using Prisma and Next.js, and I'm experiencing an issue where the content is not updating in real-time until I manually re-deploy the application. Here's the scenario:

  • I have an API endpoint in my Next.js app that fetches data from a Prisma database.
  • When I create or update data in the database through the application, the changes are reflected immediately in the development environment, but they are not reflected in the production environment until I re-deploy the application.

This is how I get my data on the front-end:

const { data: posts, error } = useSWR(`/api/getPosts`, fetcher, {refreshInterval:1000});

This is the API endpoint to post the content :

// Addposts to prisma backend

import { NextResponse, NextRequest } from 'next/server';
import prisma from '../../../prisma/client';

// Function

export async function POST(request:NextRequest) {
    const data = await request.json();
    const title = data.title;
    const user = await prisma.user.findUnique({
        where: {
            email : data.email
        }
    })
    if (!user){
        // return error
        return NextResponse.json({error: "User not found"}, {status: 404})
    }

    if (!title){
        // throw error
        return NextResponse.json({error: "Title is required"}, {status: 400})
    }

    if (title.length > 300){
        return NextResponse.json({error:"Title should not be more than 300 characters"}, {status:400});
    }
    const userId = user?.id;

    const post = await prisma.post.create({
        data: {
            title,
            userId
        }
    })
    try{
        return NextResponse.json({post},{status:200})
    }catch(error){
        return NextResponse.json({error}, {status:500})
    }
}


API endpoint to get all the posts:

import { NextRequest, NextResponse } from 'next/server'
import prisma from '../../../prisma/client'
import { NextApiResponse } from 'next';


export async function GET(request:NextRequest){
    const posts = await prisma.Post.findMany({
        include: {
            user: true
        },
        orderBy:{
            createdAt: 'desc'
        }
    })
    try{
        // return all the posts
        return NextResponse.json({posts},{status:200})
    }catch(error){
        return NextResponse.json(error, {status:500});
    }
}

How can I ensure that the content updates are immediately reflected in the production environment without the need for manual re-deployment?

Here is the link to the GitHub repo.

UPDATE

I am able to make POST request and make changes to the db, I think the problem has to be with the GET request since the data appears to be static even when I refresh the page.

Here is my Runtime Logs on Vercel:

Prisma 和 Next.js:内容在重新部署之前不会更新。

答案1

得分: 2

好的,以下是翻译好的部分:

在经过几天的调试之后,这是对我有效的方法:

我将我的GET和POST函数移动到一个名为route.ts的单一文件中。

之前:

这是我进行GET请求的方式:axios.get(url/api/getPosts)
以及我的POST请求:axios.post(url/api/addPosts)

之后:

这是我进行GET请求的方式:axios.get(url/api/Posts)
以及我的POST请求:axios.post(url/api/Posts)

英文:

Ok, after days of debugging, this is what worked for me:

I moved my GET and POST function into a single route.ts file.

Before:

This is how I did my GET request: axios.get(url/api/getPosts)
and my POST request axios.post(url/api/addPosts)

After:

This is how I did my GET request: axios.get(url/api/Posts)
and my POST request axios.post(url/api/Posts)

答案2

得分: 1

const { data: posts, error } = useSWR(`/api/getPosts`, fetcher, {refreshInterval:1000});

这意味着你正在使用 SWR,这是用于数据获取的 React hook。

我看到 SWR 有一个 mutation 函数 可能会有兴趣:你可以使用 SWR 的 mutate() 函数来更新缓存并重新获取数据。

从你的仓库 Sadeedpv/tweet-it 中,我看到你有一个 app/components/InputField.tsx,它处理了提交功能。它向你的 /api/addPosts 端点发出 POST 请求。

你可以修改 handleSubmit 函数以在创建帖子后重新验证 SWR 缓存,如下所示:

import { mutate } from 'swr'; // <-- 从 SWR 导入 mutate

// ...

const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setPost('');
  setDisabled(true);
  toast('Posting...');

  try {
    await axios.post("/api/addPosts", {
      title: post,
      email: session?.user?.email,
    });

    mutate('/api/getPosts'); // <-- 在这里重新验证 SWR 缓存

    setDisabled(false);
    toast.success('Successfully posted');
  } catch (err) {
    toast.error(err.response.data.error);
  }
};

// ...

通过调用 mutate('/api/getPosts'),你告诉 SWR 在 /api/getPosts 端点重新验证数据。这应该确保一旦成功创建新帖子,你的应用程序中的帖子列表立即更新。

你的 InputField.tsx 组件的其余部分可以保持不变。

当你在 InputField 组件中调用 mutate('/api/getPosts') 时,你使用了一个“使用相同键的挂载 SWR hook”,即 /api/getPosts,根据 SWR 文档的说法。这意味着缓存将会被更新,并触发重新验证,这可能是你需要的。

如果数据仍然保持不变,请尝试首先禁用服务器端缓存以进行测试:针对从你的 Prisma 数据库获取数据的特定 Next.js API 路由。你可以通过在服务器端代码中设置适当的 Cache-Control 头部 来实现这一点。

export async function GET(request: NextRequest) {
  const posts = await prisma.Post.findMany({
    include: {
      user: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  });
  try {
    // 返回所有帖子
    let response = NextResponse.json({ posts }, { status: 200 });
    response.headers.set("Cache-Control", "s-maxage=1, stale-while-revalidate");
    return response;
  } catch (error) {
    return NextResponse.json(error, { status: 500 });
  }
}

s-maxage=1, stale-while-revalidate 缓存控制指令告诉服务器将响应缓存 1 秒,并且如果缓存已经过期,可以在后台提供过期的数据同时重新验证缓存。参见Rishi Raj Jain的文章 "Next.js | SWR (Stale While Revalidate) — Introduction"。

此外:

我想知道这是否与我设置 PrismaClient 的方式有关

根据 Vercel 如何管理无服务器函数实例,可能是 Prisma Client 的旧实例导致问题。你可以尝试确保为每个请求创建一个新的 Prisma Client 实例:

import { PrismaClient } from "@prisma/client";

export default function getPrismaClient() {
  const client = new PrismaClient();
  return client;
}

然后在你的 API 路由中,你可以这样做:

import getPrismaClient from '../../../prisma/client';

export async function GET(request: NextRequest) {
  const prisma = getPrismaClient();
  const posts = await prisma.Post.findMany({
    include: {
      user: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  });
  // ...
}

不要忘记在使用后断开 Prisma Client,以避免任何潜在的连接问题:

// ...
const posts = await prisma.Post.findMany({
  include: {
    user: true,
  },
  orderBy: {
    createdAt: 'desc',
  },
});
prisma.$disconnect();
// ...

我尝试了更新后的解决方案,但不幸的是,它没有解决我的问题。

那么你需要更多的调试信息:

在你的 API 端点中添加 console.log 语句以跟踪请求和响应。这可以帮助你了解 API 请求是否正常工作以及响应数据是否如预期。

export async function GET(request: NextRequest) {
  const prisma = getPrismaClient();
  const posts = await prisma.Post.findMany({
    include: {
      user: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  });
  prisma.$disconnect();

  console.log("Posts received from DB:", posts); // 记录从数据库收到的数据

  try {
    // 返回所有帖子
    let response = NextResponse.json({ posts }, { status: 200 });
    response.headers.set("Cache-Control", "s-maxage=1, stale-while-revalidate");
    return response;
  } catch (error) {
    console.log("GET Request Error:", error); // 记录任何潜在错误
    return NextResponse.json(error, { status: 500 });
  }
}

注意:服务器less函数(如Vercel的API路由)上的控制台日志不会出现在浏

英文:

const { data: posts, error } = useSWR(/api/getPosts, fetcher, {refreshInterval:1000});

That means you are using SWR, the React hook for data fetching.

I see SWR has a mutation function which would be of interest: You can use mutate() function of SWR to update cache and re-fetch the data.

From your repository Sadeedpv/tweet-it, I see you have a app/components/InputField.tsx, which handles the submit function. It makes a POST request to your /api/addPosts endpoint

You can modify the handleSubmit function to also revalidate the SWR cache after the post is created, like so:

import { mutate } from &#39;swr&#39;; // &lt;-- import mutate from SWR

// ...

const handleSubmit = async (e: React.FormEvent) =&gt; {
  e.preventDefault();
  setPost(&#39;&#39;);
  setDisabled(true);
  toast(&#39;Posting...&#39;);

  try {
    await axios.post(&quot;/api/addPosts&quot;, {
      title: post,
      email: session?.user?.email,
    });

    mutate(&#39;/api/getPosts&#39;); // &lt;-- revalidate SWR cache here

    setDisabled(false);
    toast.success(&#39;Successfully posted&#39;);
  } catch (err) {
    toast.error(err.response.data.error);
  }
};

// ...

By calling mutate(&#39;/api/getPosts&#39;), you are telling SWR to revalidate the data at the /api/getPosts endpoint. That should ensure that your list of posts is immediately updated in your app once a new post is successfully created.

The rest of your InputField.tsx component can stay the same.

When you call mutate(&#39;/api/getPosts&#39;) in your InputField component, you are using a "mounted SWR hook using the same key" (/api/getPosts), as per the SWR documentation. That means that the cache will be updated, and a revalidation will be triggered, which is what you might need here.


That might be lighter on your server than include a 'no-store' cache option in your fetch request, like:

const fetcher = async (url: string) =&gt; {
  const response = await fetch(url, { cache: &#39;no-store&#39; });
  const data = await response.json();
  return data.posts;
};

If you find that your app is serving stale data due to Next.js's default caching behavior, you could use the no-store option to bypass the cache and always fetch fresh data from the server.


If the data remains static, try and
disable first the server-side caching, for testing: do that for the specific Next.js API route that fetches data from your Prisma database. You can achieve this by setting appropriate Cache-Control headers in your server-side code.

export async function GET(request:NextRequest){
    const posts = await prisma.Post.findMany({
        include: {
            user: true
        },
        orderBy:{
            createdAt: &#39;desc&#39;
        }
    })
    try{
        // return all the posts
        let response = NextResponse.json({posts},{status:200});
        response.headers.set(&quot;Cache-Control&quot;, &quot;s-maxage=1, stale-while-revalidate&quot;)
        return response;
    }catch(error){
        return NextResponse.json(error, {status:500});
    }
}

The s-maxage=1, stale-while-revalidate cache-control directive tells the server to cache the response for 1 second and, if the cache is stale, to serve the stale data while revalidating the cache in the background.
See "Next.js | SWR (Stale While Revalidate) — Introduction" from Rishi Raj Jain.

In addition:

> I wonder if this has anything to do with the way I set up my PrismaClient

Depending on how Vercel manages serverless function instances, it may be that a stale instance of Prisma Client is causing issues. You can try to ensure that a new Prisma Client instance is created for each request:

import { PrismaClient } from &quot;@prisma/client&quot;

export default function getPrismaClient() {
  const client = new PrismaClient();
  return client;
}

And then in your API routes, you would do:

import getPrismaClient from &#39;../../../prisma/client&#39;

export async function GET(request:NextRequest){
    const prisma = getPrismaClient();
    const posts = await prisma.Post.findMany({
        include: {
            user: true
        },
        orderBy:{
            createdAt: &#39;desc&#39;
        }
    })
    ...
}

Remember to disconnect the Prisma Client after using it to avoid any potential connection issues:

...
const posts = await prisma.Post.findMany({
        include: {
            user: true
        },
        orderBy:{
            createdAt: &#39;desc&#39;
        }
    })
prisma.$disconnect();
...

> I tried the updated solution, but unfortunately, it didn't solve my problem.

Then you need more debug information:

Add console.log statements in your API endpoints to track the requests and responses. That can help you understand if the API requests are working correctly and whether the response data is as expected.

export async function GET(request:NextRequest){
    const prisma = getPrismaClient();
    const posts = await prisma.Post.findMany({
        include: {
            user: true
        },
        orderBy:{
            createdAt: &#39;desc&#39;
        }
    })
    prisma.$disconnect();

    console.log(&quot;Posts received from DB:&quot;, posts); // Logging the received data from DB

    try{
        // return all the posts
        let response = NextResponse.json({posts},{status:200});
        response.headers.set(&quot;Cache-Control&quot;, &quot;s-maxage=1, stale-while-revalidate&quot;)
        return response;
    }catch(error){
        console.log(&quot;GET Request Error:&quot;, error); // Logging any potential error
        return NextResponse.json(error, {status:500});
    }
}

Note: console logs on serverless functions (like Vercel's API routes) will not appear in the browser's console. You will need to check Vercel's function logs for these. You can access these logs through your Vercel dashboard.

If you have not already, try testing your API routes locally using a tool like Postman. That can help isolate whether the problem is with your code or the deployment environment.

And confirm that your Prisma Client can connect to your database correctly. You can add a check when your API routes start up to see if they can connect to the database.

// At the beginning of your API routes
const prisma = getPrismaClient();
await prisma.$connect()
  .then(() =&gt; console.log(&quot;Connected to DB&quot;))
  .catch(error =&gt; console.log(&quot;DB Connection Error: &quot;, error));
// ... rest of your code

You can also add onSuccess and onError callbacks to your SWR hook to help debug potential issues.

const { data: posts, error } = useSWR(`/api/getPosts`, fetcher, {
  refreshInterval: 1000,
  onSuccess: (data) =&gt; console.log(&quot;Data received by SWR:&quot;, data),
  onError: (error) =&gt; console.log(&quot;SWR Error:&quot;, error),
});

Depending on the information you gather, it might be a problem with the API endpoints, the connection to the database, or the data handling on the client side.


To add to the OP's solution:

Both SWR and Next.js have their own caching mechanisms:

  • SWR, by default, will cache the data it fetches and revalidate (refetch) the data when the component remounts, the browser regains focus, or the network is reconnected.

  • Next.js (on top of SWR) has a built-in data fetching and caching feature. However, since the cache is local to each function, it may not share state across multiple serverless functions.

The Next.js serverless function for each endpoint might have created a new instance of the Prisma client. As a result, the Prisma client in each function may not have been aware of changes made by the Prisma client in the other function.
Combining the GET and POST operations into one function ensures that they share the same instance of the Prisma client, and therefore both have the same view of the database.

Before, you had two separate endpoints, one for getting posts (/api/getPosts) and one for adding posts (/api/addPosts).
After the change, you consolidated these into a single endpoint (/api/Posts) that handles both GET and POST requests.

This is actually a common pattern in RESTful API design, where a single URL or endpoint is mapped to a resource, and the type of HTTP method (GET, POST, PUT, DELETE, etc.) determines the action to be taken on that resource.

For example:

  • GET /api/Posts: Fetches the list of posts
  • POST /api/Posts: Creates a new post

The benefit of this approach is that it simplifies the API structure and makes it easier to understand what each endpoint does based on standard HTTP methods.

答案3

得分: 0

这是因为您的GET处理程序没有使用请求,并且在生产中它是静态生成的。无论您是否使用SWR,您的API处理程序都是纯HTML,它始终保持不变,返回在构建时预取的相同数据。

export const dynamic = "force-dynamic";

在GET API处理程序中将解决此问题
英文:

This is because your GET handler is not using request, and in production it is statically generated (https://nextjs.org/docs/app/building-your-application/routing/router-handlers). No matter are you using SWR or not - your api handler is plain html, it always remains same, returning same data that was prefetched at build time.

export const dynamic = &quot;force-dynamic&quot;;

in GET api handler will solve the issue.

huangapple
  • 本文由 发表于 2023年7月18日 04:08:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76707774.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定