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 esm and system output with env=development, min=false and an inline map or none, and production output only for a module that builds a production conditional. It answers anything else with 400 OPTION_UNSUPPORTED. If you omit env and min, 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:

JavaScriptsame-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:

Shell
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.mjs

Check 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