• בלוג
  • ריענון אוטומטי למידע צד שרת ב next.js

ריענון אוטומטי למידע צד שרת ב next.js

07/04/2025

קומפוננטות צד שרת ב node יכולות לעשות דברים מופלאים, למשל לקרוא קובץ ממערכת הקבצים ולהציג אותו בקלות על המסך או לקרוא מידע מבסיס הנתונים. הבעיה מתחילה כשנתוני צד השרת האלה מתעדכנים. פעם כשהיתה לנו קומפוננטת צד לקוח שקראה את המידע עם react-query ידענו לרענן את המידע כל כמה שניות או לבנות Web Socket כדי לקבל עדכון מהשרת כשהמידע מתעדכן. בפוסט היום נראה דוגמה קצרה איך לעשות דבר דומה ובקלות בקומפוננטות צד שרת.

1. קומפוננטת צד שרת שמציגה תוכן של קובץ

הקוד הבא ב page.tsx הוא קומפוננטת צד שרת שמציגה תוכן של קובץ ולידה תיבת טקסט:

import fs from 'node:fs/promises';

export default function Home() {
  const data = fs.readFile('README.md', 'utf-8')
  return (
    <div>
      <input type="text" />
      <div>
        {data}
      </div>
    </div>
  )
}

2. ריענון אוטומטי מתוך קומפוננטת צד לקוח

כאשר התוכן על הדיסק משתנה אין שום שינוי על המסך. המשתמש יראה את התוכן החדש רק כשילחץ ריענון. לפעמים זה מספיק טוב אבל לפעמים אנחנו כן רוצים עדכון אוטומטי. דרך אחת קלה להציג תמיד את המידע העדכני היא להפעיל "reload" של next באופן יזום. אני כותב קומפוננטת צד לקוח חדשה בשם AutoRefresh עם התוכן הבא:

'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

export default function AutoRefresh() {
  const router = useRouter();
  useEffect(() => {
    const interval = setInterval(() => {
      router.refresh();
    }, 1000);

    return () => clearInterval(interval);
  }, [router]);

  return null;
}

ומוסיף את הקומפוננטה ל page:

import fs from 'node:fs/promises';
import AutoRefresh from './auto-refresh';

export default function Home() {
  const data = fs.readFile('README.md', 'utf-8')
  return (
    <div>
      <AutoRefresh />
      <input type="text" />
      <div>
        {data}
      </div>
    </div>
  )
}

ועכשיו הדפדפן שלי מושך כל שניה את הקומפוננטה המרונדרת מהשרת ומעדכן את העמוד עם התוכן החדש. כיף לשים לב שאין דריסה של ה input, כלומר קומפוננטות צד לקוח שעודכנו בצד הלקוח ישמרו על ערכן למרות הריענון.

3. מה הלאה

כמובן שטעינה מחדש כל שניה יוצרת עומס מיותר על השרת. אנחנו יכולים לשפר את המנגנון אם נוסיף SSE או Websockets שישלחו ללקוחות הודעה כל פעם שיש שינוי במידע בצד שרת, ואז הדפדפן יטען מחדש את העמוד רק אחרי שמקבל הודעה כזו.

החיסרון השני בגישה זו הוא שלא נרצה לשים את קומפוננטת AutoRefresh יותר מפעם אחת ביישום. מנגנון יותר מוצלח יהיה להשתמש ב Provider כדי להפעיל את האפקט ולשמור כמה קומפוננטות צריכות את הריענון. כל קומפוננטה שתשלב את AutoRefresh תעלה את המונה ב-1 ואותו Provider יפעיל את הטיימר רק אם מספר הקומפוננטות שמשתמשות ב AutoRefresh בעמוד גדול מ-1.

בגישת ה Provider אני כותב קובץ בשם refresh-provider.tsx עם התוכן הבא:

'use client';
import { useEffect, useState, createContext } from 'react';
import { useRouter } from 'next/navigation';
export const RefreshContext = createContext((n: number) => {})
export default function RefreshProvider({ children }: { children: React.ReactNode }) {
  const [clients, setClients] = useState(0);
  const router = useRouter();

  const addClients = (n: number) => setClients(c => c + n);

  useEffect(() => {
    if (clients > 0) {
      const timer = setInterval(() => {
        router.refresh();
      }, 1000);
      return () => {
        clearInterval(timer);
      }
    }
  }, [router, clients > 0]);
  return (
    <RefreshContext value={addClients}>
      {children}
    </RefreshContext>
  );
}

אני מעדכן את layout.tsx כדי להשתמש בפרוביידר:

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${geistSans.variable} ${geistMono.variable}`}>
        <RefreshProvider>
          {children}
        </RefreshProvider>        
      </body>
    </html>
  );
}

ומעדכן את auto-refresh באופן הבא:

'use client';
import { useEffect, use } from 'react';
import { useRouter } from 'next/navigation';
import { RefreshContext } from '@/lib/refresh-provider';

export default function AutoRefresh() {
  const router = useRouter();
  const addClients = use(RefreshContext)

  useEffect(() => {
    addClients(1);
    return () => addClients(-1);
  }, [router]);

  return null;
}

עכשיו לא משנה כמה פעמים נוסיף אותו לעמוד ה interval ירוץ רק פעם אחת, וכשלא יהיו יותר אלמנטי Auto Refresh על העמוד הריענון האוטומטי ייפסק.