CSS & JavaScript

How to handle assets for use in a web browser

Browser assets are stored in two distinct folders, based on whether these assets require processing or not.

Static Assets

Any assets that do not require processing (e.g., fonts, robots.txt, icons) should be placed in the /public directory. They will be served as static files from the root directory.

Route pathURL
public/robots.txt/robots.txt
public/fonts/ubuntu.woff2/fonts/ubuntu.woff2

CSS & JavaScript

CSS, JavaScript, or TypeScript files that require processing and minification should be stored in the src directory and must be named as index.[css|js|jsx|ts|tsx]. Only index files serve as bundle entries and are accessible to the client. This approach offers flexibility in creating a custom directory layout.

Please note: Jeasx doesn't specify any hardcoded outbase directory for esbuild, so it defaults to the lowest common ancestor directory for all your browser assets. So when you put all your assets into a dedicated folder (e.g. src/browser), browser will be removed from the resulting path.

Route pathURL
src/index.css/index.css
src/custom/index.js/custom/index.js
src/utils/date.jsThis file will be not available via an url.

Please note: as JavaScript or TypeScript is compiled to ECMAScript modules (ESM) via esbuild, you should add type="module" to your script tags to avoid subtle errors.

Intercept static assets with reply.file

All static files - whether stored in the public directory or the dist folder - are delivered via the Jeasx route pipeline immediately following the guard processing stage. This architecture allows you to intercept static file requests seamlessly. You can access static file metadata directly within guards via reply.file. If a file exists at the requested path, this object is pre-populated with headers, statusCode, and stream. This enables you to apply or bypass authentication, modify headers, or replace content on the fly.

export default async function ({ request, reply }) {
  if (reply.file) {
    console.log(reply.file.statusCode, reply.file.headers, reply.file.stream);
    // Delete header
    delete reply.file.headers["ETag"];
    // Overwrite existing header
    reply.file.headers["Cache-Control"] = "public, no-cache";
  }

  // Don't respond with static "/robots.txt"
  if (request.path === "/robots.txt") {
    reply.file = undefined;
  }
}