Relative references must start with either “/”, “./”, or “../”

Sean Bradley
Jan 20 · 5 min read
Photo by Lacie Slezak on Unsplash

What does that even mean?

It is the difference between using Module Specifiers Versus Relative Import References in your client side JavaScript ES6 imports.

The error typically happens when using Module Specifiers in your client side import statements. The browser does not understand Module Specifiers by default.

The error you see in the console describes “relative references”. It is expecting a URL.

In this article, I outline the differences of using Relative Import References versus Module Specifiers.

I will use Threejs ES6 module references in the example code.

eg, using Module Specifiers

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

Versus using Relative Import References

import * as THREE from '/build/three.module.js';
import { OrbitControls } from '/jsm/controls/OrbitControls';

Relative Import References use Uniform Recourse Locators (URL).

URLs are one of the fundamental building blocks of the internet and using Relative Import References to download your resources is ok. People have been using URLs to download resources from web servers since before the internet even went public. Read the spec at https://www.w3.org/Addressing/URL/url-spec.txt

To import modules client side using the Module Specifiers method, there are a few different steps involved with setting up your project structure. Without these steps you are likely to see the error in the browser console similar to

Uncaught TypeError: Failed to resolve module specifier "three". 
Relative references must start with either "/", "./", or "../".

When writing your import statements in your preferred IDE (VSCode, Webstorm, ATOM, etc) and using Module Specifiers, you normally won’t see an error underlining your import statements. The IDE will scan the node_modules folder using various rules and attempt to auto link to the Module Specifier that you wrote in your import statements.

You only become aware of the error when you finally try to run your code in the browser. For many developers, beginner and experienced, this doesn’t make much sense, and can be a major source of confusion with many people giving up or trying to resolve there problems by asking questions on internet forums.

Module Specifiers rely on a Module Resolution traversing strategy. This doesn’t work by default in browsers since the browser doesn’t have direct access to the file system on the server in order to traverse all the folders and try all the methods of finding references that are involved during Module Resolution. If this was to run from the browser, it would trigger many 404 errors to be returned to the client as it tries out all the possible rules for finding references. You can read more about Module Resolution strategies at TypeScript Module Resolution

A common approach to solving this problem in the browser is to introduce a step that bundles all the required imports into one JavaScript file to be used on the client side. The client loads this JavaScript bundle and sets up all the module namespaces, for example three, into the browsers memory ready for use.

The tool that is used is commonly called a bundler. There are many bundlers that we can choose from to add to our projects, eg Webpack, Parcel, Rollup, Browserify and many more.

Note that when bundling all the code and imports at compile time, a Module Resolution strategy will not be necessary for the browser to support in order to get Module Specifiers to work on the browser. All the code required by the client should already be sorted in memory ready for referencing for when the page has fully downloaded.

When using Relative Import References, it is also not necessary for the browser to understand any Module Resolution strategy, since you are already explicitly telling the browser where it can locate its resource by using a specific URL in the import statement. No guess work is required by the browser.

When using Relative Import References in your client scripts to download resources from your own web server, you need to help the webserver find and return that resource otherwise you will get the 404 error response. 404 errors are one of the most common errors beginners will see when learning about web development. It is simply, that the server cannot find the resource you asked from it.

If you are asking for Threejs libs from a server, and it is your server and it is returning 404 errors, then that means you need to help the server figure out where these resources are so that it can return them to the client requesting.

There are many ways to help the webserver. You can manually copy files to web accessible folders, you can create symlinks, or IIS virtual directories, or even static routes using the options available in your web sever technology. And there are many more options for selectively switching packets based on payload information. I use NodeJS so I can easily use Express static routes, but there many options.

You may also use the option of importing from other external web servers by using the full domain name/IP and path,

eg, these imports below target a specific Threejs release from a public CDN

import * as THREE from 'https://unpkg.com/three@0.120.1/build/three.module.js';
import { OrbitControls } from 'https://unpkg.com/three@0.120.1/jsm/controls/OrbitControls';

Working Code Examples

I have several Threejs boilerplates that demonstrate using Relative Import References and Module Specifiers.

The first boilerplate uses Relative Import References and Express Static Routes to serve the Threejs Libraries.

The second boilerplate, focuses on using TypeScript and also uses Relative Import References with Express Static Routes, but also contains a branch using WebPack which uses the Module Specifiers technique.

To view the WebPack branch named statsguiwebpack,

git clone https://github.com/Sean-Bradley/Three.js-TypeScript-Boilerplate.git Threejs-TS-Webpack
cd Threejs-TS-Webpack
git checkout statsguiwebpack
npm install -g typescript
npm install
npm run dev

See the README.md in each or the boilerplates root folders for more concise setup instructions.

I hope that this article has helped you to understand a little more about the differences between Relative Import References and Module Specifiers, and that the error messages you will see make a little more sense.

For more information on using ES6 modules in Threejs, you can read my other article Importing Three.js as Module.

Or if you want learn in much more detail about Threejs and TypeScript then checkout my official course at Udemy https://www.udemy.com/course/threejs-tutorials/?referralCode=4C7E1DE91C3E42F69D0F.

Thanks for reading my article, always remember to Clap, Comment and Share and I will write more.

Sean

Threejs Tutorials

Learn Threejs

Sean Bradley

Written by

Developer of real time, low latency, high availability, asynchronous, multi threaded, remotely managed, fully automated and monitored solutions.

Threejs Tutorials

Many short tutorials featuring Threejs

Sean Bradley

Written by

Developer of real time, low latency, high availability, asynchronous, multi threaded, remotely managed, fully automated and monitored solutions.

Threejs Tutorials

Many short tutorials featuring Threejs

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store