SEO & visibility

Load JavaScript and CSS for the task that actually needs them

Five complete files, four artificial tasks and twenty actual Edge cases connect on-demand loading with useful initial content.

Symbolic readable task table with a separate JS and CSS module for the sorting action.

A page does not need to download every feature in full when it first opens. It does need the content and presentation required for the first meaningful task. We build a readable HTML table whose sorting feature loads only when someone actually uses it. An executed browser experiment covers successful loading, a missing module and unavailable supplemental CSS. All records are artificial teaching values. We do not convert observed request counts into invented loading-time improvements.

Define the first useful task

The example shows four fictional tasks with planned minutes. A person opening the page should be able to read those names and values immediately. Sorting is additional assistance, not a prerequisite for understanding the information. That business distinction determines the technical boundary. The table and its essential styles belong to the initial document. The sorting algorithm can wait for a deliberate later action. Without this decision, loading on demand would be a technical slogan without an observable definition of demand.

The initial row order is Planung, Abgleich, Nachweis and Prüfung. Two tasks have equal planned values, so the additional feature also needs a stable tie rule. An arbitrary order might initially look harmless but become confusing during repeated sorting. We therefore use an internal identifier as the secondary sort key. Those identifiers are fictional records, not real work orders. The planned minutes are not measured execution times of the software either. They are input values for a small deterministic exercise.

Deliver useful HTML before enhancement

The HTML file contains the complete table, caption, header cells and four rows. A browser does not need a separate JSON service before it can display those existing values. With JavaScript disabled, the same information remains available. The unavailable sorting button initially stays hidden and becomes visible when the small entry module installs its event handler. This avoids presenting an apparently usable control which silently does nothing when script execution is unavailable. The basic reading task remains independent of the enhancement.

This foundation is not a complete application without JavaScript. It fulfils only the deliberately chosen reading task. If sorting without JavaScript were also required, a server-side request could provide it. Our teaching case does not pretend to implement that second capability. It demonstrates a clear distinction between existing information and an optional feature. A real service page needs to derive the same boundary from its actual tasks instead of classifying every interaction as expendable merely because it can be written in JavaScript.

Separate essential presentation from additional presentation

The base.css file provides readable typography, table spacing, colours, keyboard focus and handling of limited space. It is linked immediately. The optional feature.css only adds visible emphasis to the sorting-status message. If that file fails, the table, button and focus remain usable. This separation matters: a deferred file must not secretly contain the only rule which makes a control already shown to the person understandable or visibly focused. Essential presentation cannot be made optional simply by renaming its file.

The link-element documentation explains external stylesheet inclusion. Our own flow creates an additional link element only during the first sorting action. We do not infer that a request happened merely from a filename or media condition. The independent HTTP server records the actual resource request. Essential styles remain separate and are requested at the beginning of each ordinary example view. This makes the distinction inspectable through server evidence rather than a diagram based only on intended code structure.

An entry module is not automatically on-demand loading

The document links main.mjs as a module. This small entry point locates existing elements, reveals the control and responds to its use. If it statically imported the sorting algorithm, that algorithm would remain in the initial dependency graph. Splitting code into named files would not itself change this. Our entry point instead retains an expression for a later dynamic import. The expression is not evaluated before the sorting action, which gives the chosen boundary a specific trigger.

The MDN script-element reference distinguishes module execution, defer and async. Those mechanisms concern fetching and evaluating linked scripts. They do not replace the decision about whether a feature belongs to the initial task. In our case, sorting first becomes necessary through an explicit action. Another application could have a different boundary, such as essential navigation which nearly every visitor needs immediately. Moving that behaviour into a later file would require a different justification and acceptance test.

Connect first use to import()

When needed, the handler evaluates import('./sorter.mjs') and retains its Promise. Once loading succeeds, the exported function can reorder the existing rows. The import() reference describes dynamic loading and its failures. A failed request must not be treated as successful preparation. In particular, our table cannot announce that it has been sorted before the sorting code has arrived and produced the intended order. Request initiation and feature completion are distinct states.

Before first use, the actual origin log has no request for /sorter.mjs or /feature.css. After the first successful action, it has one request for each. Another sort produces no additional requests for those two files in the observed document. We retain the import result and create the additional stylesheet only once. This is an observation about the supplied implementation. It does not claim what files an arbitrary bundler emits, discovers or preloads in a different application architecture.

Observed stageSorting moduleSupplemental CSSContent
Page openedNo request yetNo request yetFour rows already exist
First sorting actionOne actual requestOne actual requestAscending planned values
Second sorting actionNo additional requestNo additional requestDescending planned values
Three recorded loading states: no optional module initially, one module and CSS request on first sort and no additional request on second sort.
The three states come from a fresh Edge context. All four rows are already available when the page opens. Only the first sorting action requests the additional module and stylesheet. Sorting again creates no further request for those files. The orders and failure cases were actually checked; this diagram contains no time or performance metric.

Protect an in-progress action against duplicate execution

The first module request can still be in progress when another activation occurs. Two quick activations should not cause conflicting sorts. The handler therefore records a busy state and ignores further actions until the first one finishes. It also sets aria-disabled="true" on the control. That attribute does not replace the JavaScript guard. The condition at the beginning of the handler is what actually prevents another activation from initiating more work while the feature is unavailable.

The test server deliberately holds the module response. A second keyboard activation occurs while that response remains pending. Row order stays unchanged and the server still records only one request for the module. We then release that specific response and the original action completes. This is a controlled flow experiment, not a measured comparison of different internet speeds. An arbitrary fixed wait is not presented as an actual visitor's loading time. The hold exists to make an otherwise brief intermediate state observable.

Respect focus while the response is pending

Sorting does not move keyboard focus elsewhere. Someone remaining on the control can continue using it afterwards. Someone moving to the next link while the module is pending should remain on that link when the response arrives. The implementation therefore does not call a focus method merely as a precaution. A late response should not pull a person back to an earlier task after they have deliberately continued navigation. Preserving the meaningful current task is more useful than always returning to the trigger.

This condition was actually exercised. While the module was held, keyboard navigation moved to the next link. After release and sorting, that link remained active. The table also receives a busy indication during the pending state, which is removed afterwards. A status region describes the completed order. These are targeted state and focus checks. They do not replace testing every supported assistive technology or every interaction route in a production application, and no such comprehensive support claim is derived from them.

Keep the loaded module's responsibility narrow

The loaded module has one bounded task: order existing rows by planned minutes and identifier. It requests no further data and does not replace the entire table with newly assembled HTML. The original names and values consequently remain present. Ascending order is Abgleich, Prüfung, Nachweis and Planung. For the equal planned values, identifier 21 precedes 22. Descending order retains that same secondary tie rule. The direction changes the primary numerical comparison, not the meaning of the identifiers or their role in the example.

The program reads numbers from controlled data attributes and performs simple safe-integer checks. It is not a universal validator for arbitrary external records. An application receiving or editing values from elsewhere needs separate handling of that source. Here, the useful property is a clearly defined module responsibility. Small file size alone would not establish that the algorithm is safe or correct. We independently check actual result orders rather than assuming a sensible-looking rendered table proves the intended numerical comparison.

Expose the actual order clearly

After success, the handler updates the sorting property on the relevant header cell. The control's text identifies the next available direction, while the status region describes the order just produced. Those messages serve different purposes and should not be collapsed into one unexplained changing arrow. Colour alone could also conceal the meaning of a change. Our example retains text and a corresponding property so that the result remains understandable beyond the decorative treatment added by the optional stylesheet.

The WAI sortable-table example demonstrates semantic tables and aria-sort while also discussing limitations of illustrative code. Our own implementation has a smaller single sortable column. We do not turn that reference into a promise about every screen reader. A real product review needs suitable assistive technologies and complete application flows. The present experiment establishes the recorded browser states and keyboard actions of its own code, which is a narrower and more defensible statement.

Handle a module failure without losing content

Another test mode actually returns 404 for the module request. The table subsequently remains in source order. It does not display a false sorting-success message or a new sorting attribute on the header. The status explains that the enhancement could not be loaded while the existing information remains readable. In this teaching contract, the failed optional action is not offered again as though another click already constituted a verified recovery path. A clear bounded error state is preferable to implying an untested capability.

A larger application could provide controlled retry or a server-side alternative. That would require additional decisions, including the state of already evaluated modules and the exact version being requested. This example does not promise those additional paths. Its documented behaviour is more useful than a polished retry control whose response to an evaluation failure has never been inspected. The original reading task stays available, and the unsuccessful enhancement is distinguished from a successful operation with no visible data change.

Distinguish a stylesheet failure from a feature failure

In the stylesheet countercheck, only the optional CSS request fails. The sorting module loads normally and the rows are ordered correctly. The extra status decoration is absent, but basic readability and keyboard focus remain. This case actually challenges the initial division between essential and supplemental presentation. If the result became incomprehensible without that file, the supposedly optional stylesheet would contain something essential, and the load boundary would need revision rather than an excuse about an unusual network condition.

The business operation does not wait for the decorative stylesheet. That is appropriate only because the base file already presents all necessary information usefully. Another component might depend on additional CSS for meaningful operation. Its loading and error flow would need to reflect that difference. A technical preference for deferring more resources should not override the actual task of the person operating the component. The acceptance condition is a usable result through the chosen flow, not simply a later timestamp on a request.

Read the five complete files together

The example contains an HTML document, base styling, the entry module, the sorting module and optional feature styling. The following versions match the files actually tested. The test server served JavaScript modules with an appropriate media type and used an available loopback port. Opening a file directly from storage is not equivalent evidence about the same module path and server responses. To run the example, serve the files together through an HTTP server which maps the demonstrated resource paths consistently.

No connection to real customer records exists. All four tasks and planned values are fictional. The observed action changes only the order of those existing elements. It neither approves work nor records time. This distinction matters in a visually convincing example: a professionally presented row should not appear to document an actual customer job. The teaching data make the interface behaviour inspectable without borrowing private records or implying outcomes which the experiment did not perform.

example-en.html

<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Compare tasks</title><link rel="stylesheet" href="/base.css"><script type="module" src="/main.mjs"></script><main><h1>Compare tasks</h1><div class="table-wrap"><table><caption>Four fictional tasks with planned minutes. The minutes column can be sorted.</caption><thead><tr><th scope="col">Task</th><th id="minutes-heading" scope="col"><span>Minutes</span> <button id="sort" type="button" hidden>Sort minutes ascending</button></th></tr></thead><tbody><tr data-id="17" data-minutes="45"><th scope="row">Planung</th><td>45</td></tr><tr data-id="8" data-minutes="15"><th scope="row">Abgleich</th><td>15</td></tr><tr data-id="22" data-minutes="30"><th scope="row">Nachweis</th><td>30</td></tr><tr data-id="21" data-minutes="30"><th scope="row">Prüfung</th><td>30</td></tr></tbody></table></div><p class="order-note" id="status" role="status">Source order</p><a href="#details">Further information</a><h2 id="details">Further information</h2><p>The planned values belong to an artificial teaching example.</p></main></html>

base.css

*{box-sizing:border-box}body{margin:0;background:#fff9ef;color:#242a2e;font:18px/1.5 Arial,sans-serif}main{max-width:860px;margin:0 auto;padding:24px}h1{font-size:clamp(25px,4vw,38px)}.table-wrap{max-width:100%;overflow:auto}table{border-collapse:collapse;width:100%;background:#fff}caption{text-align:left;margin-bottom:16px}th,td{padding:12px;text-align:left;border-bottom:1px solid #777}th[scope=row]{font-weight:400}button{font:inherit;font-size:16px;padding:10px;background:#b80024;color:#fff;border:2px solid transparent;cursor:pointer}button:focus-visible,a:focus-visible{outline:3px solid #242a2e;outline-offset:4px}button[aria-disabled=true]{opacity:.7}a{color:#a90022}[hidden]{display:none!important}@media(prefers-color-scheme:dark){body{background:#15191c;color:#fff9ef}table{background:#20262a}a{color:#ffb4c0}button:focus-visible,a:focus-visible{outline-color:#fff9ef}}

main.mjs

const button=document.querySelector('#sort'),table=document.querySelector('table'),status=document.querySelector('#status'),heading=document.querySelector('#minutes-heading');
const en=document.documentElement.lang==='en',labels=en?{loading:'Preparing sorting.',asc:'Minutes sorted ascending.',desc:'Minutes sorted descending.',error:'Sorting could not be loaded. The table remains readable.',nextAsc:'Sort minutes ascending',nextDesc:'Sort minutes descending'}:{loading:'Sortierung wird vorbereitet.',asc:'Minuten aufsteigend sortiert.',desc:'Minuten absteigend sortiert.',error:'Sortierung konnte nicht geladen werden. Die Tabelle bleibt lesbar.',nextAsc:'Minuten aufsteigend sortieren',nextDesc:'Minuten absteigend sortieren'};
let busy=false,failed=false,modulePromise,styleRequested=false,nextAscending=true;
button.hidden=false;
button.addEventListener('click',async()=>{
 if(busy||failed)return;busy=true;button.setAttribute('aria-disabled','true');table.setAttribute('aria-busy','true');status.textContent=labels.loading;
 if(!styleRequested){styleRequested=true;const link=document.createElement('link');link.rel='stylesheet';link.href='/feature.css';document.head.append(link);}
 try{modulePromise??=import('./sorter.mjs');const {reorder}=await modulePromise;reorder(table,nextAscending);heading.setAttribute('aria-sort',nextAscending?'ascending':'descending');status.textContent=nextAscending?labels.asc:labels.desc;status.classList.add('sorted');nextAscending=!nextAscending;button.textContent=nextAscending?labels.nextAsc:labels.nextDesc;}
 catch{failed=true;status.textContent=labels.error;}
 finally{busy=false;table.removeAttribute('aria-busy');if(!failed)button.removeAttribute('aria-disabled');}
});

sorter.mjs

export function reorder(table,ascending){const rows=[...table.tBodies[0].rows];for(const row of rows)if(!Number.isSafeInteger(Number(row.dataset.minutes))||!Number.isSafeInteger(Number(row.dataset.id)))throw new TypeError('Invalid teaching data');rows.sort((a,b)=>(ascending?1:-1)*(Number(a.dataset.minutes)-Number(b.dataset.minutes))||Number(a.dataset.id)-Number(b.dataset.id));for(const row of rows)table.tBodies[0].append(row);}

feature.css

.order-note.sorted{border-inline-start:6px solid #b80024;padding-inline-start:12px;font-weight:700}

Understand the twenty browser cases

The checks used fresh Edge contexts for German and English, viewport widths 320, 390 and 1440, and light and dark presentation. Each language additionally covered disabled JavaScript, a missing module, missing supplemental CSS and a deliberately pending module response. Twenty cases passed 226 individual assertions. The actual browser version was 157.0.4341.42, and the HTTP fixture server ran with Node.js 24.22.1. Fresh contexts kept previously loaded modules from making the initial request counts misleading.

Observed results include row order, tie handling, requests for specific files, busy state, sorting property, focus and absence of page-level overflow. This was not a complete assessment of every imaginable table feature. No loading duration, field INP or general mobile-device speed was measured. The small example gives a concrete technical explanation, not statistical evidence about how often visitors to a real site would use sorting. A product decision needs appropriate evidence about that actual use as well as correct implementation mechanics.

Derive the loading boundary from the product

The web.dev introduction to code splitting discusses separating initially needed scripts from later functionality. A general argument for smaller initial work does not finish our product decision. We also need to know when the feature is needed and what waiting or failure states it introduces. Infrequent optional sorting can justify a different boundary from a central checkout step which nearly everyone needs immediately. The same technique can have very different consequences for those two tasks.

A real optimisation connects observed use with a suitable technical change and renewed verification. Preparing a frequently needed component early can be sensible. An additional request is not inherently a defect either. The relevant unit is the complete task, including success and recovery. Our experiment keeps its boundary simple so that the observation is easy to explain. It does not demand that every application be split into as many small files as possible or assume that more filenames necessarily mean a better experience.

Plan publication and resource versions together

A file requested later still needs to match the main document which is already open. If a release immediately deletes older module files, an existing page could fail only when the person next activates a feature. A real rollout therefore needs coordinated naming, retention and rollback decisions. Our isolated experiment performs no production deployment and claims no executed verification of that longer transition. It identifies the responsibility which a deployment process must address before applying the same technique to real sessions.

One successful reuse within the open document also differs from a new navigation. A new document environment performs its own preparation, and whether an HTTP-cache response participates is a separate layer. The fixture deliberately sends non-reusable HTTP responses while the successful module remains available to the running document. That distinction prevents the absence of a second observed module request from being presented as a complete explanation of every cache state. The evidence belongs to the particular sequence which was actually executed.

Evaluate file count and actual work separately

Fewer initial requests do not automatically mean fewer transferred bytes. One large file may transfer more than several small, relevant resources. Conversely, each added dependency has its own failure and publication path. A real optimisation should consider transferred content, required evaluation and the person's task together. Our demonstration is an inspectable starting point, not a universal rule based on counting filenames. It shows how to observe a boundary before deciding whether that boundary offers a worthwhile trade-off in the actual application.

The sorting function is intentionally tiny. Separating such a small feature may deserve a different practical decision from deferring a large optional analysis tool. The experiment explains the mechanism and its limits. It does not claim a business benefit for these four fictional rows. A product team would test the same kind of boundary using its actual feature and suitable measurements before turning the example into a lasting architectural decision or presenting it as evidence of a measured customer outcome.

Checklist for reliable on-demand loading

  • Identify the first meaningful task and its necessary content.
  • Provide essential presentation and keyboard focus rules early.
  • Distinguish file splitting from genuinely later requests.
  • Connect first need to a clear permitted action.
  • Guard in-progress work against duplicate execution.
  • Preserve focus and navigation when responses arrive late.
  • Test modules and optional stylesheets with actual error responses.
  • Keep existing information available when enhancement fails.
  • Separate observed file requests from measured loading durations.
  • Include already open documents in resource-version deployment plans.

Reliable on-demand loading is more than a shorter initial file list. It delivers the original information, enhances it at the appropriate moment and has an understandable failure path. Those properties can be checked in the small sorting example. The next decision is which comparable boundary makes sense in the real product and what additional evidence is needed to measure its effect there.

Let’s move forward

What would you like to change?

An idea, a specific challenge or your next step with AI. We’re listening.

Let’s talk