How to Load Data Without Refreshing the Page Using jQuery AJAX
How I load data without a page refresh using jQuery AJAX: .load() with a fragment selector, a JSON "Load more" button, live search that aborts stale requests, a spinner, IE caching, delegated handlers and a working back button.

The question I get: “Clicking a link or typing in a box reloads my whole page. I want only the list or the panel to change. How do I load data without page refresh in jQuery, without breaking the back button or search engines?” The mechanics are easy. The details around them are where pages go wrong.
To load data without a page refresh, you send an HTTP request from JavaScript and put the response into an existing element. jQuery gives you two routes. .load() fetches HTML and inserts it. The lower-level $.ajax() family fetches data you format yourself. The .load() documentation covers the first. My jQuery AJAX tutorial covers the second, with the failure cases.
I ran every snippet below on jQuery 1.11.0 against a local test server, and the output is real. The test page has a #target panel, an empty #list, a “Load more” button, a search box and a #status line.
Links first, then load data without page refresh
My position: build the page to work with ordinary links and forms first, then add AJAX on top. If page two exists only behind a JavaScript click handler, search engines never see it. Neither does a visitor with a broken script, or someone who wants to bookmark it. Progressive enhancement is what makes the AJAX version safe to ship.
Route 1: .load() for a ready-made HTML fragment
// .load() with a selector after the URL: only that part of the response is inserted
$('#target').load('/fragment.html #news', function (responseText, textStatus, jqXHR) {
console.log('status :', textStatus, jqXHR.status);
console.log('inserted HTML :', $('#target').html());
console.log('script executed :', window.__ran === undefined ? 'no (scripts are stripped when a selector is used)' : 'yes');
});
status : success 200
inserted HTML : <div id="news"><p>Story one</p><p>Story two</p></div>
script executed : no (scripts are stripped when a selector is used)
The line to notice is the URL: '/fragment.html #news', with a space and a selector after the address. jQuery fetches the whole response but inserts only the element matching #news. This is the quickest way to load data without page refresh when a normal, full page already exists. The same URL serves a complete page to a browser and a fragment to your script. The callback receives the response text, a status and the jqXHR object, as in a normal request.
The output shows a side effect too. The response contained an inline <script>, and with a selector jQuery removed it before inserting. Without a selector, the behaviour changes:
// .load() without a selector: the whole response goes in, and inline scripts run
$('#target').load('/fragment.html', function () {
console.log('children :', $('#target').children().map(function () { return this.id; }).get().join(', '));
console.log('script executed :', window.__ran === 1 ? 'yes, once' : window.__ran);
});
children : header, news,
script executed : yes, once
Loading the whole response ran the script once. That matters for security. .load() without a selector runs whatever script the server sends. So point it only at your own server’s markup, and never at a URL that includes text a user typed. The empty entry at the end of the children list is the script element jQuery ran and then removed.
Route 2: load data without page refresh as JSON
var $btn = $('#more');
$btn.on('click', function () {
var next = $btn.data('next');
$btn.prop('disabled', true);
$.getJSON('/page/' + next)
.done(function (data) {
$.each(data.items, function (i, text) { $('#list').append($('<li>').text(text)); });
if (data.next) { $btn.data('next', data.next).prop('disabled', false); }
else { $btn.hide(); }
})
.fail(function () { $btn.prop('disabled', false); });
});
$btn.click();
setTimeout(function () {
console.log('after click 1:', $('#list li').length, 'items | button disabled:', $btn.prop('disabled'), '| next =', $btn.data('next'));
$btn.click();
setTimeout(function () {
console.log('after click 2:', $('#list li').map(function () { return $(this).text(); }).get().join(','), '| button visible:', $btn.css('display') !== 'none');
}, 200);
}, 250);
after click 1: 3 items | button disabled: false | next = 2
after click 2: one,two,three,four,five | button visible: false
Here the server returns data instead of markup: {"items": [...], "next": 2}. The click handler asks for the page named in a data-next attribute. It appends one list item per entry with .text(), so a title with angle brackets cannot turn into markup. Then it updates the button. The button stays disabled while the request runs, so a double-click does not fetch page two twice. When the server says there is no next page (next: null), the button hides. The server decides when to stop, and the script never guesses. This is how I load data without page refresh on most list pages.
I pick JSON when the same data feeds more than one view, when the HTML would be large next to the data, or when I want to format values in the browser. HTML fragments win when the markup is complex, your server templates already build it, and you want one place to maintain it. If you insert many items, build the markup as one string and insert it once.
Live search: cancel the old request
var current = null, log = [];
function search(term, ms) {
if (current) { current.abort(); }
current = $.getJSON('/slow', { ms: ms, q: term })
.done(function () { log.push('done(' + term + ')'); })
.fail(function (xhr, status) { log.push('fail(' + term + ':' + status + ')'); });
}
search('j', 400);
search('jq', 300);
search('jque', 100);
setTimeout(function () { console.log(log.join(' ')); }, 700);
fail(j:abort) fail(jq:abort) done(jque)
This bug appears only on slow connections. A visitor types “j”, “jq”, “jque”, and three requests fly out. Responses do not arrive in order. The answer for “j” can land after the answer for “jque”, and the page shows results for the wrong text. The fix: keep the current request in a variable and call .abort() on it before starting the next. In the output, jQuery aborted the first two requests (their .fail() ran with abort), and only the latest one delivered data. Treat abort as a normal outcome in your .fail() handler and show no error for it.
Start the request after a short pause instead of on every key press. Wait about 250 milliseconds after the last keystroke with setTimeout and clearTimeout. Live search is the hardest place to load data without page refresh well. The server then sees one request per word instead of one per letter.
A loading indicator for every request
var events = [];
$(document).ajaxStart(function () { events.push('ajaxStart'); $('#status').text('Loading…'); })
.ajaxStop(function () { events.push('ajaxStop'); $('#status').text(''); });
$.getJSON('/json'); $.getJSON('/text');
setTimeout(function () { console.log(events.join(' -> '), '| status text now:', JSON.stringify($('#status').text())); }, 300);
ajaxStart -> ajaxStop | status text now: ""
Two requests started together produced one ajaxStart and one ajaxStop. These events mean “any request in flight,” so they suit a single spinner or a “Loading…” line, however many requests overlap. Any time you load data without page refresh, show that something is happening. If the visitor sees nothing, they click again, and you get duplicate requests.
Internet Explorer and cached responses
$.ajaxSetup({ cache: false });
$.getJSON('/count', { a: 1 }).done(function (d) {
console.log('query sent with cache:false ->', d.query.replace(/_=\d+/, '_=<timestamp>'));
});
query sent with cache:false -> ?a=1&_=<timestamp>
With cache: false, jQuery adds a changing _=timestamp parameter to GET requests, so the URL is new every time. Without it, Internet Explorer often serves a stored copy of a GET response, and a “refresh the list” button seems to do nothing. Old IE is the main reason a page fails to load data without page refresh after the first request. Set it with $.ajaxSetup({ cache: false }) for a page of live data, or per request.
The handlers that disappear
$('#target').html('<a class="x">old</a>');
$('#target .x').on('click', function () { console.log('direct handler fired'); });
$('#target').on('click', '.x', function () { console.log('delegated handler fired'); });
$('#target').html('<a class="x">new</a>'); // replaced, like a .load() would
$('#target .x').click();
delegated handler fired
When new content replaces old content, the handlers bound to the old elements go with it. Only the delegated handler fired. If your loaded content has links or buttons, attach the handler once to #target, or another container you never replace, with the selector as the second argument. I explain why in my post on jQuery event handling. It is also a regular among jQuery interview questions.
Keep the back button and search engines working
An AJAX update changes the page but not its address. That causes three problems. The back button leaves the site. The visitor cannot bookmark or share what they see. And search engines see only the first state. Every page that loads data without page refresh has to solve them. There are two answers, and your choice depends on the browsers you support.
- The History API.
history.pushStateexists in current Chrome, Firefox and Safari, and in Internet Explorer 10 and later, but not Internet Explorer 9 or earlier. Call it after each update to give the new state a real URL. Listen forpopstateto reload the right content when the visitor goes back. The History API reference on MDN describes both. Feature-test withif (window.history && history.pushState)and fall back to normal links. - Real links underneath. Make “next page” an ordinary
<a href="/news?page=2">that works without JavaScript, and intercept the click to load it with AJAX. The server returns a full page for a normal request. It returns a fragment when it sees theX-Requested-With: XMLHttpRequestheader that jQuery adds to every AJAX call. Crawlers and visitors without scripts follow the link and get a complete page.
Hashbang URLs
Older sites keep state after a # in the URL. Google documents a “hashbang” convention (#!) that lets its crawler request a static copy. That scheme works, but you must build a second, crawlable version of every page. Real links are simpler and do not depend on a crawler feature. Whichever you pick, test it. View the page with JavaScript off and check that the content is still reachable.
Also tell assistive technology that content changed. Put aria-live="polite" on the container that updates, so a screen reader announces new results.
Troubleshooting partial page loads
| Symptom | Cause | Fix |
|---|---|---|
| The whole page appears inside the panel | No selector after the URL in .load() |
Use '/page #content' or return a fragment for AJAX requests |
| Script in loaded content did not run | A selector was used, so scripts are stripped | Move behaviour into the main page and delegate events |
| Results do not match the text typed | Responses arrived out of order | Abort the previous request before starting a new one |
| Same data after clicking refresh in Internet Explorer | GET cached | cache: false |
| Buttons in the new content do nothing | Direct handlers on replaced elements | Delegate from a stable container |
| Back button leaves the site | URL never changed | Use pushState and popstate, or real links |
| Nothing happens when testing locally | Page opened from file:// |
Test through a local web server |
How I load data without page refresh on real sites
- One URL, two responses. The same address returns a full page normally and only the fragment for AJAX, based on the
X-Requested-Withheader. You maintain one template. - The server decides when to stop. Paged endpoints return a
nextvalue or nothing, and the script hides the button when it is missing. - Debounced search with cancellation. A short pause after typing, one request in flight at a time, and the previous one aborted.
- Visible loading and failure states. A spinner while waiting, and a message with a retry button when the request fails. Never a silent, empty list.
The failure I see most often with partial loading is the live search that shows the wrong results. It works on the developer’s fast office connection, so nobody notices. Then a visitor on a slow mobile link types quickly, an early response lands last, and the list shows matches for half a word. Adding .abort() takes three lines. Testing with the browser’s network throttling is how I catch it before visitors do.
How should this update load?
- Is there a plain link or form that already works? Build that first, and enhance it.
- HTML or JSON? A fragment when the server already renders it and the markup is complex. JSON when several views share the data.
- Can several requests overlap? If yes, keep the current request and abort the old one.
- Does the visitor need the new state to have an address? If yes, use
pushStatewhere available, or real links. - What does the visitor see while waiting and on failure? Decide both before writing the success path.
When not to load data without page refresh
- Content search engines must index. Put important text in the first HTML response instead of fetching it after load.
- A change that is really a new page. If the visitor thinks of it as going somewhere else, they expect a normal navigation with its own address.
- Tiny lists. If the full list is a few items, include it in the page and show or hide it.
Common mistakes
- Building a page that only works with JavaScript. Start with real links.
- Not aborting the old request. Out-of-order responses show the wrong results.
- Inserting server text with
.html(). Use.text()for anything a user could have written. - Binding handlers to elements that will be replaced. Delegate.
- Leaving out the loading and failure states. The visitor cannot tell slow from broken.
- Ignoring the back button. A new state needs a new URL.
If you would rather load data without page refresh and skip jQuery, the native XMLHttpRequest object does the same job with more code.
FAQ
How do I load data into a div without refreshing the page in jQuery?
Call $('#target').load('/url #fragment') to fetch HTML and insert only the matching part. Or use $.getJSON(url) and build the markup yourself in .done(). Either way, add a loading indicator and handle failure.
What does jQuery .load() do with a selector after the URL?
It fetches the whole response but inserts only the elements that match the selector, such as /page.html #news. jQuery strips inline scripts from the response when you use a selector, and runs them when you do not.
How do I build a “Load more” button with jQuery?
On click, request the next page with $.getJSON('/page/' + next) and append the items with .text(). Store the next page number from the response, and hide the button when the server reports no next page. Disable the button while the request runs.
How do I stop old AJAX results from overwriting new ones?
Keep the current request in a variable and call .abort() on it before starting the next. The aborted request’s .fail() callback runs with textStatus equal to abort. Ignore that case instead of showing an error.
How do I keep the back button working with AJAX content?
Where the browser supports it, call history.pushState after each update and handle popstate to restore the earlier content. Also make the navigation real links with their own URLs, so pages work without JavaScript and search engines can reach them.
Loading part of a page takes a little jQuery and a lot of care. Cancel stale requests, show progress, delegate handlers, and keep a real address for every state. My rule of thumb when I load data without page refresh: make it work with links first, let AJAX make it faster, and abort anything the visitor has already moved past.
Originally posted on 18 March 2014 and post last modified on 2 November 2026