Test loading skeletons with simulated latency
Use ?delay=N to make Go REST take seconds to respond, so you can verify your skeleton screens render without flicker.
Skeleton screens look great when they actually appear. The problem in development is that requests on localhost return in 20 milliseconds, so you never see the skeleton at all. The first time you hit production with a slow connection, you discover that your skeleton flickers, mis-sizes, or never renders. This recipe fixes that by giving you control over how long the API takes to respond, without changing any production code.
The trick
Append?delay=N to any Go REST GET request, and the API pauses N milliseconds before responding (capped at 5000). TheX-Simulated-Delay-Ms response header confirms the simulation fired.
curl -i "https://gorest.co.in/public/v2/users?delay=2000"
Two seconds is enough to see your skeleton; ten seconds tells you if it gets stuck. Combined with throttling in DevTools (Network tab → throttling preset → "Slow 4G"), you can simulate real network conditions accurately.
A working React component
Standard pattern: render skeleton whenusers === null, real data otherwise. Setusers to[] if the call returns no rows, so the empty state and loading state stay distinct.
function UserList() {
const [users, setUsers] = useState(null);
useEffect(() => {
fetch("https://gorest.co.in/public/v2/users?delay=2000")
.then(r => r.json())
.then(setUsers);
}, []);
if (users === null) {
return (
<ul>
{Array.from({ length: 6 }).map((_, i) => (
<li key={i} className="skeleton-row">
<div className="skeleton skeleton-avatar" />
<div className="skeleton skeleton-line" />
</li>
))}
</ul>
);
}
return (
<ul>
{users.map(u => (
<li key={u.id}>{u.name} ({u.email})</li>
))}
</ul>
);
}
The CSS
A simple shimmer keyframe is enough. Tune the colours to your design system; the key is the moving gradient, which signals "loading, not just blank".
.skeleton {
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton-avatar { width: 32px; height: 32px; border-radius: 50%; }
.skeleton-line { height: 16px; flex: 1; }
Avoid skeleton flicker on fast responses
If your real-world latency averages 80 ms, the skeleton appears for 80 ms and then jumps to data, which is jarring. Two fixes:
- Delay the skeleton. Render nothing for the first 200 ms, then show the skeleton if the request is still pending. Users perceive instant responses; the skeleton only shows when there is actually a wait.
- Minimum skeleton duration. Keep the skeleton on screen for at least 400 ms even if data arrives faster, so it does not flash.
Code for the second:
async function fetchWithMinDuration(url, minMs = 400) {
const [data] = await Promise.all([
fetch(url).then(r => r.json()),
new Promise(r => setTimeout(r, minMs))
]);
return data;
}
Test the layout shift
Skeleton sizes should match the real content sizes; otherwise the layout shifts when data arrives. Set explicitheight /width on the skeleton elements that match the rendered content. Lighthouse's Cumulative Layout Shift metric will catch mistakes if you forget.
Other simulation flags
The simulation parameters compose. To test "slow + then 500", combine them:
fetch("/public/v2/users?delay=2000&force_status=500")This pauses 2 seconds and then returns 500. Useful for testing skeleton + error state in sequence.
Production checklist
- Skeleton renders at exactly the right size (no layout shift).
- Delay is tied to render, not to mount; if the user navigates away, cancel the request.
- Minimum visible duration so the skeleton does not flash on fast networks.
- Distinct empty state: skeleton means "loading", empty list means "no results".
- Error state takes precedence over skeleton; if the fetch fails, show the error UI immediately.
Keep building
Test 401, 422, 429 and 500 handlers
Force any status code with ?force_status= to make sure your error UI never shows blank screens or eats stack traces.
Build a paginated user list in React
A working component that fetches a page, renders rows, and uses X-Pagination-Pages to render Previous/Next correctly.
Build infinite scroll with IntersectionObserver
Append pages as the sentinel element scrolls into view. Works with any list endpoint that returns pagination headers.