Assets

Handling assets like images, videos, etc. in content-driven sites.

When building content-driven websites, you often have assets like images, videos, or other files that belong to a specific page or section. Managing these assets can become difficult if they are all stored in a central directory.

The AssetManager allows you to co-locate your assets with your content files. It handles the resolution of relative paths in your content and data, ensuring that they point to the correct location in the final build.

Additionally, it can process your assets during the build, like adding a hash for cache busting, scaling and optimizing images and more.

Setup

To use the AssetManager, you need to set it up in your main.dart file.

  1. Create an instance of AssetManager.
  2. Add the assetManager.middleware to the server app.
  3. Add assetManager.dataLoader and assetManager.pageExtension to your ContentApp.
import 'package:jaspr/server.dart';
import 'package:jaspr_content/jaspr_content.dart';

void main() {
  // 1. Initialize and configure AssetManager.
  final assetManager = AssetManager(
    // The root directory where your assets are located. Usually, this is the same as your content directory.
    directory: 'content', 
    // Optional: Configure which properties in your frontmatter contain asset paths.
    dataProperties: {'image', 'meta.thumbnail'}, 
  );

  // 2. Add middleware to serve assets during development.
  ServerApp.addMiddleware(assetManager.middleware);

  runApp(ContentApp(
    // ...
    dataLoaders: [
      // ...
      // 3a. Process relative asset paths in page data.
      assetManager.dataLoader,
    ],
    extensions: [
      // ...
      // 3b. Process relative asset paths in page content (markdown/html).
      assetManager.pageExtension,
    ],
  ));
}

Directory Structure

With AssetManager, you can organize your assets alongside your content.

content/
  images/
    logo.png
  blog/
    my-post.md
    image.png
    cover.jpg
    media/
      thumbnail.jpg
      video.mp4

Usage

Once set up, you can reference your assets using relative or absolute paths. The AssetManager will automatically resolve these paths to the correct absolute URL, both during development and in the static build.

  • Relative asset paths are resolved relative to the current page's directory. This allows you to organize your assets alongside your content files.

  • Absolute paths are resolved relative to the asset root directory.

In Content (Markdown/HTML)

You can use relative paths in standard HTML tags like <img>, <video>, <audio>, and <source>. This works in both Markdown files and raw HTML.

<!-- In content/blog/my-post.md -->

# My Post

Here is an image located in the same directory:
![My Image](image.png)

And here is a video in a subdirectory:
<video src="media/video.mp4" controls></video>

This is an image in another directory:
![Logo](/images/logo.png)

These would resolve to:

  • content/blog/image.png
  • content/blog/media/video.mp4
  • content/images/logo.png

In Frontmatter

If you reference assets in your page's frontmatter (e.g., for a thumbnail or cover image), you need to tell the AssetManager which properties to process using the dataProperties option during setup.

---
title: My Post
image: cover.jpg
meta:
  thumbnail: media/thumbnail.jpg
---

With dataProperties: {'image', 'meta.thumbnail'}, AssetManager will resolve cover.jpg and media/thumbnail.jpg relative to the content file.

In Components

When building custom components, you can resolve asset paths using the context.resolveAsset() extension method.

import 'package:jaspr/jaspr.dart';
import 'package:jaspr_content/jaspr_content.dart';

class MyComponent extends StatelessComponent {
  @override
  Component build(BuildContext context) {
    // Resolve 'image.png' relative to the current page.
    final imagePath = context.resolveAsset('image.png');
    // You can also resolve absolute paths (relative to the asset root).
    final logoPath = context.resolveAsset('/images/logo.png');

    return .fragment([
      img(src: iconPath),
      img(src: logoPath),
    ]);
  }
}

Asset Transformers

Assets are processed during the build. By default, AssetManager uses the HashingAssetTransformer, which adds a content hash to the filename for cache busting (e.g., image.a1b2c3d4.png).

You can customize this behavior or add your own transformers by passing a list of AssetTransformers to the AssetManager constructor.

final assetManager = AssetManager(
  directory: 'content',
  assetTransformers: [
    // Add a custom transformer.
    MyCustomTransformer(),
    // Apply the default hashing behavior.
    HashingAssetTransformer(),
  ],
);

Custom Transformers

To create a custom transformer, extend AssetTransformer and implement the transform method.

class MyCustomTransformer extends AssetTransformer {
  @override
  Asset transform(Asset asset, [Object? aspect]) {
    // Transform the asset (e.g. compress images, optimize svg, etc.)
    // Return a new Asset or the same asset.
    return asset;
  }
}

The aspect option is passed through as a parameter from context.resolveAsset(aspect: ...) and can be used to generate asset variants, such as differently resized images.