javascript 43 lines · 7 steps

Archiving with router.refresh in Next.js

A client component archives a project, then refreshes server data without a full page reload.

Explained by highlit
1'use client';
2 
3import { useRouter } from 'next/navigation';
4import Link from 'next/link';
5import { useState, useTransition } from 'react';
6 
7export default function ProjectRow({ project }) {
8 const router = useRouter();
9 const [isPending, startTransition] = useTransition();
10 const [archiving, setArchiving] = useState(false);
11 
12 async function archive() {
13 setArchiving(true);
14 try {
15 const res = await fetch(`/api/projects/${project.id}/archive`, {
16 method: 'POST',
17 });
18 if (!res.ok) throw new Error('Failed to archive project');
19 
20 startTransition(() => {
21 router.refresh();
22 });
23 } finally {
24 setArchiving(false);
25 }
26 }
27 
28 return (
29 <li className="project-row">
30 <Link
31 href={`/projects/${project.id}`}
32 prefetch={false}
33 onMouseEnter={() => router.prefetch(`/projects/${project.id}`)}
34 >
35 {project.name}
36 </Link>
37 
38 <button onClick={archive} disabled={archiving || isPending}>
39 {archiving ? 'Archiving\u2026' : 'Archive'}
40 </button>
41 </li>
42 );
43}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1router.refresh re-fetches server components in place, so a mutation's effects appear without a full navigation.
  2. 2Wrapping the refresh in startTransition keeps the UI responsive and exposes an isPending flag for disabling controls.
  3. 3Deferring prefetch to onMouseEnter avoids eager network work while still making navigation feel instant on intent.

Related explainers

javascript
const STORAGE_KEY = "theme-preference";
 
function getSystemTheme() {
  return window.matchMedia("(prefers-color-scheme: dark)").matches

Building a dark-mode toggle that respects the OS

dark-mode localstorage matchmedia
Intermediate 8 steps
javascript
import { useState, useRef, useCallback } from "react";
 
export function MultiSelect({ options, value, onChange, placeholder = "Select…" }) {
  const [open, setOpen] = useState(false);

Building a keyboard-accessible MultiSelect in React

controlled-component accessibility keyboard-navigation
Intermediate 10 steps
javascript
async function uploadInBatches(records, uploadFn, { batchSize = 100, concurrency = 3 } = {}) {
  const batches = [];
  for (let i = 0; i < records.length; i += batchSize) {
    batches.push(records.slice(i, i + batchSize));

Uploading records with bounded concurrency

concurrency worker-pool async-await
Advanced 8 steps
javascript
function formatPhoneNumber(value) {
  const digits = value.replace(/\D/g, '').slice(0, 10);
  const parts = [];
 

Building a live phone number input mask

input-masking regex dom-events
Intermediate 7 steps
javascript
import { NextResponse } from 'next/server';
 
const locales = ['en', 'fr', 'de', 'es'];
const defaultLocale = 'en';

Locale routing with Next.js middleware

middleware i18n content-negotiation
Intermediate 10 steps
javascript
const express = require('express');
const cookieParser = require('cookie-parser');
 
const router = express.Router();

Remember-me login with signed cookies in Express

authentication signed-cookies sessions
Intermediate 9 steps

Share this explainer

Here's the card — post it anywhere.

Archiving with router.refresh in Next.js — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code