How to have a server component timestamp update on the client side.
Unanswered
West African Lion posted this in #help-forum
West African LionOP
I'm using NextJS 14 and have a conditional render that depends on the time. When an event is edited, added, or deleted on a specific Google calendar, Google calls a route handler that triggers a website revalidation to reflect those changes.
I'm running into an issue with the following part of the site:
Since this code runs server-side, after the revalidation is triggered, the "current" time it compares to isn't updated with each refresh, so the time never changes. How can I force a specific part of the server component to update on each refresh client side? Any other suggestions for this issue would be greatly appreciated!
I'm running into an issue with the following part of the site:
<h2 className="text-5xl font-normal col-span-5 mb-2">
{moment().isBefore(nextEvent.opening)
? `Opening ${moment(nextEvent.opening).fromNow()}!`
: `We Are Open!`}
</h2>Since this code runs server-side, after the revalidation is triggered, the "current" time it compares to isn't updated with each refresh, so the time never changes. How can I force a specific part of the server component to update on each refresh client side? Any other suggestions for this issue would be greatly appreciated!
57 Replies
@West African Lion I'm using NextJS 14 and have a conditional render that depends on the time. When an event is edited, added, or deleted on a specific Google calendar, Google calls a route handler that triggers a website revalidation to reflect those changes.
I'm running into an issue with the following part of the site:
tsx
<h2 className="text-5xl font-normal col-span-5 mb-2">
{moment().isBefore(nextEvent.opening)
? `Opening ${moment(nextEvent.opening).fromNow()}!`
: `We Are Open!`}
</h2>
Since this code runs server-side, after the revalidation is triggered, the "current" time it compares to isn't updated with each refresh, so the time never changes. How can I force a specific part of the server component to update on each refresh client side? Any other suggestions for this issue would be greatly appreciated!
Is this page a dynamically rendered page or a statically rendered page?
If statically rendered: you cannot make it run something whenever a request comes, since it’s static. Then you could use a client component and update the text based on the time in the client component.
A different approach would be to add something like export const revalidate = 0 to make the page dynamic. But then everything will become dynamic unless you explicitly cache the data e.g. by force-cache in fetch or by unstable_cache.
If dynamically rendered: it should just work.
If statically rendered: you cannot make it run something whenever a request comes, since it’s static. Then you could use a client component and update the text based on the time in the client component.
A different approach would be to add something like export const revalidate = 0 to make the page dynamic. But then everything will become dynamic unless you explicitly cache the data e.g. by force-cache in fetch or by unstable_cache.
If dynamically rendered: it should just work.
West African LionOP
I attached the component code
West African LionOP
@joulev Is this page a dynamically rendered page or a statically rendered page?
If statically rendered: you cannot make it run something whenever a request comes, since it’s static. Then you could use a client component and update the text based on the time in the client component.
A different approach would be to add something like export const revalidate = 0 to make the page dynamic. But then everything will become dynamic unless you explicitly cache the data e.g. by force-cache in fetch or by unstable_cache.
If dynamically rendered: it should just work.
West African LionOP
When Google Calendar detects a change with any of the events, it makes a call to my route handler, which looks like this:
This works as intended, but since the moment object is created on the backend, it isn't updated on refresh which means that the page doesn't updates
import { revalidatePath } from 'next/cache'
export async function POST() {
revalidatePath('/')
return Response.json({ revalidated: true, now: Date.now() })
}This works as intended, but since the moment object is created on the backend, it isn't updated on refresh which means that the page doesn't updates
This is my staging page:
https://staging--silverperkcoffee.netlify.app/
https://staging--silverperkcoffee.netlify.app/
@West African Lion When Google Calendar detects a change with any of the events, it makes a call to my route handler, which looks like this:
ts
import { revalidatePath } from 'next/cache'
export async function POST() {
revalidatePath('/')
return Response.json({ revalidated: true, now: Date.now() })
}
This works as intended, but since the moment object is created on the backend, it isn't updated on refresh which means that the page doesn't updates
so you want the page to update live? like, if the user is already viewing the page, they will receive the new info after Google triggers the route handler?
that's not possible with server components.
for true live-style updates, you need to do real time data fetching with SSE/websocket.
to "fake" live updates, you should use client-side rendering with e.g. swr/react-query. they allow you to revalidate very regularly ensuring fresh data.
for true live-style updates, you need to do real time data fetching with SSE/websocket.
to "fake" live updates, you should use client-side rendering with e.g. swr/react-query. they allow you to revalidate very regularly ensuring fresh data.
@joulev so you want the page to update live? like, if the user is already viewing the page, they will receive the new info after Google triggers the route handler?
West African LionOP
I don't mind if they need to refresh, but at the moment, refreshing doesn't actually do anything.
I believe I found a solution thanks to your suggestion, and moved the clock logic to a client-component. So worst case, if they are having issues, they can refresh. The issue I just realized this clarified is that after an event ends, nothing is revalidated so the site stays the same even after an event ends a day prior. So I guess I need to revalidate at the end of each day? Seems like an unreasonable solution, no?
I believe I found a solution thanks to your suggestion, and moved the clock logic to a client-component. So worst case, if they are having issues, they can refresh. The issue I just realized this clarified is that after an event ends, nothing is revalidated so the site stays the same even after an event ends a day prior. So I guess I need to revalidate at the end of each day? Seems like an unreasonable solution, no?
so you get a time from the server, and you want to show ui based on that time on the client, and refresing is allowed
the reason it breaks for you is because the server time !== client time
am i correct till here @West African Lion
the reason it breaks for you is because the server time !== client time
am i correct till here @West African Lion
West African LionOP
I'm so bad at communicating, I'm sorry! Let me try again 🙏
So the server has the following function (server-side)
This fucntion only runs when the site is built, so after a deploy, nothing will update. If no changes are made on the calendar, this list won't update and the UI won't change.
I attached a picture of the UI. You can see the countdown. I was able to move that countdown compoenent into it's own file and mark it as client-side, so it updates now after a refresh. My issue is that after an event ends, the UI doesn't update even if it has the next event cached. Ideally, anytime an event ends, it would update or something, but I'm unsure how to do that without exposing the API key on the client side 😦
So the server has the following function (server-side)
async function getNextEvents() {
const calendar = google.calendar({
version: "v3",
auth: process.env.GOOGLE_API_KEY as string,
});
const events = (await calendar.events
.list({
calendarId: process.env.CALENDAR_ID as string,
timeMin: new Date().toISOString(),
maxResults: 4,
singleEvents: true,
orderBy: "startTime",
})
.catch((err) => {
console.log("Error fetching events");
console.error(err);
})
.finally(() => [])) as { data: { items: EventType[] } };
const futureEvents = events?.data.items
.reverse()
.map(
(event: {
id: any;
summary: any;
location: string;
start: { dateTime: any };
end: { dateTime: any };
htmlLink: any;
}) => ({
id: event.id,
title: event.summary || event.location.split(",")[0],
location: event.location,
opening: event.start.dateTime,
closing: event.end.dateTime,
calLink: event.htmlLink,
})
) as EventInfo[];
const nextEvent = futureEvents?.length > 0 ? futureEvents.pop() : null;
return {
nextEvent,
futureEvents: futureEvents.reverse(),
};
}This fucntion only runs when the site is built, so after a deploy, nothing will update. If no changes are made on the calendar, this list won't update and the UI won't change.
I attached a picture of the UI. You can see the countdown. I was able to move that countdown compoenent into it's own file and mark it as client-side, so it updates now after a refresh. My issue is that after an event ends, the UI doesn't update even if it has the next event cached. Ideally, anytime an event ends, it would update or something, but I'm unsure how to do that without exposing the API key on the client side 😦
I hope that clarifies things?
The hard thing is testing solutions since anything running on localhost is revalidated each refresh :/
after the event ends, what happens?
do you want it show the next event from the future events?
do you want it show the next event from the future events?
@West African Lion
@Arinji after the event ends, what happens?
do you want it show the next event from the future events?
West African LionOP
Yeah! It shouldn't be showing events where the current time is past their end time
@West African Lion Yeah! It shouldn't be showing events where the current time is past their end time
you are def overcomplicating this lol, since all of this data is saved in the server side, you can just do you clock stuff on the client, and on the client if you see the current time is pass the event time, just go to the next event, till you can finally see the current time is less than the event time
as long as you keep the actual feching on the server, how you render the ui can be done on the client side :D
also clean ui ngl
@Arinji you are def overcomplicating this lol, since all of this data is saved in the server side, you can just do you clock stuff on the client, and on the client if you see the current time is pass the event time, just go to the next event, till you can finally see the current time is less than the event time
West African LionOP
Doesn't that mean that if they don't update the calendar, after a certain amount of time, it would say no events, since the nothing changed on the calendar, google wouldn't hit the endpoint and no revalidation would take place to get the next 4 events?
@Arinji as long as you keep the actual feching on the server, how you render the ui can be done on the client side :D
West African LionOP
Got It! I'm running into a dumb issue now with the clock, but if I can't solve it, I'll open up a new thread
@West African Lion Doesn't that mean that if they don't update the calendar, after a certain amount of time, it would say no events, since the nothing changed on the calendar, google wouldn't hit the endpoint and no revalidation would take place to get the next 4 events?
didnt you say the 4 events were fetched on build time...
how many times are you building your site xD
@Arinji didnt you say the 4 events were fetched on build time...
West African LionOP
Yeah, so that's what I mean lol! It builds when I deplay, but unless something changes with the calendar, it won't revalidate the data. So it fetches the next 4 events, let's say a few days go by, so now it only shows 1 event, since it didn't fetch the "next" 4 since the build
Does that make sense?
@West African Lion Yeah, so that's what I mean lol! It builds when I deplay, but unless something changes with the calendar, it won't revalidate the data. So it fetches the next 4 events, let's say a few days go by, so now it only shows 1 event, since it didn't fetch the "next" 4 since the build
so your issue, is both with showing the ui, and fetching the data..
is the 4 being fetched on build time a feature or a bug you cant fix?
is the 4 being fetched on build time a feature or a bug you cant fix?
like is this data ideally getting server rendered? or is it a thing of ssg, where you fetch data on build time, and thats it
@Arinji also clean ui ngl
West African LionOP
Oh and thank you so much ❤️
i feel like we are going in circles now, how about we get this done on excalidraw
https://excalidraw.com/#room=6a7ced6fe6befe3cb5d6,MvK6hy-OersiWZK2gT3RlQ
https://excalidraw.com/#room=6a7ced6fe6befe3cb5d6,MvK6hy-OersiWZK2gT3RlQ
@Arinji so your issue, is both with showing the ui, and fetching the data..
is the 4 being fetched on build time a feature or a bug you cant fix?
West African LionOP
So the number 4, comes from the request here:
Most of the time, the data doesn't change, which is why I wanted it to be SSG, and only updated as needed. I'm thinking that was the wrong choice lol!
const events = (await calendar.events
.list({
calendarId: process.env.CALENDAR_ID as string,
timeMin: moment().toISOString(),
maxResults: 4, // <------ HERE
singleEvents: true,
orderBy: "startTime",
})
.catch((err) => {
console.log("Error fetching events");
console.error(err);
})
.finally(() => [])) as { data: { items: EventType[] } };Most of the time, the data doesn't change, which is why I wanted it to be SSG, and only updated as needed. I'm thinking that was the wrong choice lol!
@Arinji i feel like we are going in circles now, how about we get this done on excalidraw
https://excalidraw.com/#room=6a7ced6fe6befe3cb5d6,MvK6hy-OersiWZK2gT3RlQ
West African LionOP
Never used this before but sure!
West African LionOP
I think I'm following so far then!
go thru that once
West African LionOP
This is what you meant about time diff between client and server?
@West African Lion This is what you meant about time diff between client and server?
yea, so the server sends back 17 hours, but according to the client it shld be 18 hours, (you shld be getting hydration errors.. idk why you arent)
ok so the model makes sense now right?
West African LionOP
Look at the start times of the other events
Yeah this makes so much more sense
we just need to figure out time
West African LionOP
Seems like I had the wrong idea of how NextJS was working with caching
did you go through the reddit post?
West African LionOP
Ugh, I feel like an idiot
I feel like that was the info I was missing to make sense of this
Let me go over stuff in the next few hours now that I have better idea of the situation
If I'm still stuck, I'll ping you here?
@Arinji yea, so the server sends back 17 hours, but according to the client it shld be 18 hours, (you shld be getting hydration errors.. idk why you arent)
West African LionOP
That was just my mistake for not refreshing
West African LionOP
So for testing, how do I tell the local dev env to act more like prod where it caches things?
I feel like the local dev env never caches?
@West African Lion So for testing, how do I tell the local dev env to act more like prod where it caches things?
which version of nextjs are you on?
our final idea btw
read that btw @West African Lion
@Arinji which version of nextjs are you on?
West African LionOP
14.X
@Arinji read that btw <@282138666023780362>
West African LionOP
Will do!