4

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:

Vercel runtime logs

Sadeed_pv
  • 513
  • 1
  • 9
  • 22

3 Answers3

2

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)

Sadeed_pv
  • 513
  • 1
  • 9
  • 22
1

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 'swr'; // <-- import mutate from SWR

// ...

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'); // <-- revalidate SWR cache here

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

// ...

By calling mutate('/api/getPosts'), 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('/api/getPosts') 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) => {
  const response = await fetch(url, { cache: 'no-store' });
  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: 'desc'
        }
    })
    try{
        // return all the posts
        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});
    }
}

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 "@prisma/client"

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

And then in your API routes, you would do:

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'
        }
    })
    ...
}

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: 'desc'
        }
    })
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: 'desc'
        }
    })
    prisma.$disconnect();

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

    try{
        // return all the posts
        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); // 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(() => console.log("Connected to DB"))
  .catch(error => console.log("DB Connection Error: ", 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) => console.log("Data received by SWR:", data),
  onError: (error) => console.log("SWR Error:", 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.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • I tried the following solution, but unfortunately, the problem still persists. It seems that the server only has access to the contents of the database at the time of deployment. I am able to make successful POST requests and alter the database, but the data appears to be static in the frontend even when I refresh the page. I wonder if this has anything to do with the way I set up my PrismaClient? – Sadeed_pv Jul 20 '23 at 17:29
  • @Sadeed_pv OK. I have edited the answer to address your comment. – VonC Jul 20 '23 at 17:54
  • Thank you for taking the time to edit your answer and addressing my comment. I tried the updated solution, but unfortunately, it didn't solve my problem. I appreciate your effort, and I'm still looking for a resolution to the issue I'm facing. If you have any other suggestions or ideas, I'd be grateful for your assistance – Sadeed_pv Jul 21 '23 at 11:11
  • @Sadeed_pv OK, I have edited the answer to include additional debug information for you to add to your code. – VonC Jul 21 '23 at 14:22
  • I have updated my question with my Deployment Runtime Logs, Also, I used Thunder Client to test my API endpoints, again my POST request is successful, but GET request failed to send me the update data @VonC – Sadeed_pv Jul 21 '23 at 18:39
  • I have found a solution, please have a look and comment your thoughts; I appreciate your quick response and valuable assistance in solving my problem. Your contribution has been incredibly helpful! – Sadeed_pv Jul 21 '23 at 19:01
0

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 = "force-dynamic";

in GET api handler will solve the issue.

Vitaly
  • 41
  • 7