albert.walickihire me
← all solutions
html

How to refresh a page automatically every few seconds?

Reload a page on a timer with meta refresh or setTimeout, why full reloads hurt users, and how to poll for fresh data and update only the DOM.

Add a <meta http-equiv="refresh"> tag to the <head> with the number of seconds and no URL. The browser reloads the page after that delay, and since the reloaded page has the same tag, it keeps doing it.

<meta http-equiv="refresh" content="30">

The JavaScript version does the same:

setTimeout(() => location.reload(), 30_000);

setTimeout is enough here, not setInterval, because the reload runs the script again and schedules the next one.

Why a full reload is usually the wrong tool

Reloading the whole page to show new data has real costs:

  • Anything the user typed into a form can be lost.
  • The scroll position can jump, especially when content above has changed.
  • The page flashes and downloads everything again.
  • Screen reader users lose their place and start again from the top.
  • If the user can't stop or delay the refresh, it fails WCAG's timing requirements.

It's fine for a status screen on a wall that nobody interacts with. For anything people read or use, update the data instead.

Better: fetch the data and update the DOM

Request only the data on an interval and replace the part of the page that changed. Pause while the tab is hidden, so a forgotten tab doesn't keep hitting your API, and skip an update while the user is typing so you don't pull the content out from under them.

const INTERVAL = 30_000;
const list = document.querySelector('.orders');
let timer;

function isTyping() {
  const el = document.activeElement;
  return el?.matches('input, textarea, select') || el?.isContentEditable;
}

function renderOrders(orders) {
  const items = orders.map((order) => {
    const li = document.createElement('li');
    li.textContent = `#${order.id}: ${order.status}`;
    return li;
  });
  list.replaceChildren(...items);
}

async function update() {
  try {
    const response = await fetch('/api/orders');
    // Don't replace the list while the user is typing somewhere
    if (response.ok && !isTyping()) {
      renderOrders(await response.json());
    }
  } catch {
    // Network error: try again on the next round
  } finally {
    clearTimeout(timer);
    if (!document.hidden) timer = setTimeout(update, INTERVAL);
  }
}

document.addEventListener('visibilitychange', () => {
  clearTimeout(timer);
  if (!document.hidden) update();
});

update();

A few details in there:

  • A setTimeout scheduled after each request finishes, instead of setInterval, means a slow response never overlaps the next one.
  • document.hidden is true when the tab is in the background or the window is minimized. The visibilitychange listener stops the timer then, and fetches fresh data right away when the user comes back.
  • The finally block schedules the next round even when the request fails.

If screen reader users need to know about new items, don't make the whole list a live region. Announce a short message instead, such as "2 new orders", in an element with aria-live="polite".

Truly live data

Polling every few seconds is simple and good enough for most dashboards. When updates must show up the moment they happen, let the server push them.

Server-Sent Events are the easier option for one-way updates. The browser keeps a connection open and reconnects on its own if it drops:

const source = new EventSource('/api/orders/stream');

source.addEventListener('message', (event) => {
  renderOrders(JSON.parse(event.data));
});

Use WebSockets when the browser also needs to send messages over the same connection, as in a chat.

more solutions
work with me

Got something that needs building?

Frontend builds, full-stack features in Django, design-system work. Available for work.

See my workGet in touch