Quickstart
Request one prepared public module from a local development server and from the CDN with a single client, changing only the base origin.
- Availability: Experimental
- Evidence: Read from source
- Tutorial
Goal
You will send one relative request to two services and compare the answers. At the end you will have seen, in your own terminal, that a module keeps its address when it moves from development to the CDN.
What you need
- Node.js 22.12 or later.
- A running Packages Dev Server that serves the package you want to request. It prints its endpoint when it becomes ready; that endpoint is your development origin.
- The origin of a CDN, and a release of an application that includes the same package version and is
ready. Preparing one is covered in the management reference. This site does not name a public CDN address. - A request whose options both services can answer. This matters, so read the next section first.
Choose a request both services can answer
The address of a module has the same form everywhere:
http://localhost:<port>/m/@example/shared@1.0.0/modules/text?target=browser&format=esm&env=development&min=false&sourcemap=none
http://localhost:<port>- Base origin: the only part you change
/m/- Namespace
@example/shared- Package
@1.0.0- Exact version
/modules/- Resource family
text- Public module subpath
?target=browser&format=esm&env=development&min=false&sourcemap=none- Options
https://cdn.beyondjs.com/m/@example/shared@1.0.0/modules/text?target=browser&format=esm&env=development&min=false&sourcemap=none
https://cdn.beyondjs.com- Base origin: the only part you change
/m/- Namespace
@example/shared- Package
@1.0.0- Exact version
/modules/- Resource family
text- Public module subpath
?target=browser&format=esm&env=development&min=false&sourcemap=none- Options
The two services do not hold the same outputs, and the contract says so openly:
- The development server produces
esmandsystemoutput withenv=development,min=falseand an inline map or none, and production output only for a module that builds a production conditional. It answers anything else with400 OPTION_UNSUPPORTED. If you omitenvandmin, you are asking for the published defaults (production, minified, external map), which a development server rejects because it never writes external maps. - The CDN holds the option sets that were prepared for the release. A valid option set that was not prepared answers
404 OUTPUT_NOT_AVAILABLE, and no build starts.
So the same relative URL returns 200 on both origins only when the release was prepared with the option set your development server accepts. Otherwise one of the two answers with the error above, and that is the correct behavior. The options reference lists every value.
Run the client
Save this file as same-url.mjs:
// One relative request, two base origins. Nothing but the origin changes between
// a local development server and the CDN.
const request = process.env.MODULE_REQUEST ?? '/m/@example/shared@1.0.0/modules/text?target=browser&format=esm';
const origins = { development: process.env.DEV_ORIGIN, published: process.env.CDN_ORIGIN };
for (const [name, origin] of Object.entries(origins)) {
if (!origin) continue;
const response = await fetch(new URL(request, origin));
const type = response.headers.get('content-type');
if (!response.ok) {
const { error } = await response.json();
console.log(`${name}: ${response.status} ${error.code} — ${error.message}`);
process.exitCode = 1;
continue;
}
const body = await response.text();
console.log(`${name}: ${response.status} ${type}, ${body.length} characters`);
console.log(` ETag: ${response.headers.get('etag')}`);
console.log(` Cache-Control: ${response.headers.get('cache-control')}`);
}Run it with your two origins and your request. Replace the package, version and subpath with a public module you actually serve:
DEV_ORIGIN="http://localhost:<port>" \
CDN_ORIGIN="<your CDN origin>" \
MODULE_REQUEST="/m/@example/shared@1.0.0/modules/text?target=browser&format=esm&env=development&min=false&sourcemap=none" \
node same-url.mjsCheck the result
When both services hold the output, you see two 200 answers with the media type application/javascript.
| Header | Development | Published |
|---|---|---|
ETag |
Strong, "sha256-…" of the body |
Strong, "sha256-…" of the body |
Cache-Control |
no-store |
public or private, with max-age, and immutable while the bytes of the URL never change |
The cache policy is the intended difference. The bytes can differ too, because a development artifact and a production artifact are not the same build; the contract does not require equal bodies for different builds.
If a service answers with an error, the client prints the status, the code and the message. Look the code up in Errors.
What you did not do
You did not change the path or the query between the two requests, you did not authenticate (the module is public), and you did not cause any compilation on the CDN. Requesting a module the CDN does not hold would have returned OUTPUT_NOT_AVAILABLE both times you asked.
Next
- Load the module in a browser with an import map: Native ESM and import maps.
- Use the same address during development and after release: Same URL, local and CDN.
- Prepare a release of your own application: Authentication and conventions.