Companion Assets (Advanced)

What are Companion Assets?

Companion assets allow you to serve static files alongside your running application during Meticulous tests. When a request matches a specific pattern, Meticulous serves the file from a local folder instead of proxying it through the secure tunnel.

This is an advanced feature that solves specific performance and deployment challenges.


When to Use Companion Assets

Next.js Applications

Problem: Next.js apps generate static assets in the .next/static/ folder during build. These assets include JavaScript bundles, CSS files, and other resources. When testing a Next.js app locally, these assets need to be available at the correct paths.

Solution: Upload the .next/static/ folder as companion assets and configure Meticulous to serve requests to /_next/static/ from this folder.

Large Static Assets

Problem: Your app has large static assets (images, fonts, videos) that slow down the secure tunnel. Transferring large files through the tunnel adds latency and can timeout.

Solution: Upload these static assets as companion assets so Meticulous serves them directly, bypassing the tunnel.

CDN-Hosted Assets During Recording

Problem: During session recording, your app loads assets from a CDN (e.g., https://cdn.example.com/assets/). During testing, you want to serve these assets locally instead of from the CDN.

Solution: Download the CDN assets locally, upload them as companion assets, and configure Meticulous to intercept CDN requests and serve them from your local folder.

Multi-Server Applications

Problem: Your application is served by multiple servers (e.g., main app on :3000, assets server on :8080). During tests, you want to consolidate asset serving.

Solution: Use companion assets to serve assets that would normally come from a separate server.


How Companion Assets Work

When you configure companion assets, Meticulous:

  1. Uploads the specified local folder to cloud storage
  2. Starts a static file server in the test environment with your assets
  3. Intercepts requests matching the regex pattern
  4. Redirects matching requests to the local asset server instead of proxying through the tunnel

Request Routing Example

Without companion assets:

Browser → /_next/static/chunks/main.js → Tunnel → Your App (localhost:3000)

With companion assets:

Browser → /_next/static/chunks/main.js → Companion Assets Server → Uploaded files

Configuration

Companion assets require two parameters, and you must provide both or neither:

1. companion-assets-folder

The path to a local folder containing the static files to upload.

  • Must be a relative or absolute path on your CI runner
  • The entire folder contents are uploaded
  • Folder structure is preserved

2. companion-assets-regex

A regular expression pattern to match request URLs that should be served from companion assets.

  • Matches against the request pathname (e.g., /_next/static/chunks/main.js)
  • Should typically start with ^ to match from the beginning
  • Only GET and HEAD requests are intercepted
  • Case-sensitive by default

You must provide both companion-assets-folder and companion-assets-regex, or neither. Providing only one will result in an error.


Complete Examples

Next.js Application

Next.js apps are the most common use case for companion assets.

GitHub Actions Workflow

name: Meticulous
on:
  push:
    branches: [main]
  pull_request: {}
  workflow_dispatch: {}

permissions:
  actions: write
  contents: read
  issues: write
  pull-requests: write
  statuses: read

jobs:
  test:
    name: Meticulous
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "24"
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build Next.js app
        run: pnpm build

      - name: Prepare companion assets
        run: |
          # Create companion assets folder
          mkdir -p companion-assets/_next
          # Copy Next.js static assets
          cp -r .next/static companion-assets/_next/

      - name: Serve Next.js app
        run: |
          pnpm start &
          sleep 5

      - name: Run Meticulous tests
        uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
        with:
          api-token: ${{ secrets.METICULOUS_API_TOKEN }}
          app-url: "http://localhost:3000"
          companion-assets-folder: "companion-assets"
          companion-assets-regex: "^/_next/static/"

Key Points:

  • Build the Next.js app first (pnpm build)
  • Copy .next/static/ to companion-assets/_next/ (preserves path structure)
  • Regex ^/_next/static/ matches all requests starting with /_next/static/
  • Folder structure in companion-assets matches URL structure

CLI Usage

# Build the app
pnpm build

# Prepare companion assets
mkdir -p companion-assets/_next
cp -r .next/static companion-assets/_next/

# Start the app
pnpm start &

# Run tests with companion assets
npx @alwaysmeticulous/cli ci run-with-tunnel \
  --apiToken="$METICULOUS_API_TOKEN" \
  --appUrl="http://localhost:3000" \
  --companionAssetsFolder="companion-assets" \
  --companionAssetsRegex="^/_next/static/"

Vite App with CDN Assets

If your Vite app loads assets from a CDN during production but you want to test with local assets:

      - name: Build Vite app
        run: pnpm build

      - name: Prepare companion assets
        run: |
          # Copy built assets
          mkdir -p companion-assets/assets
          cp -r dist/assets/* companion-assets/assets/

      - name: Serve app
        run: |
          pnpm preview &
          sleep 3

      - name: Run Meticulous tests
        uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
        with:
          api-token: ${{ secrets.METICULOUS_API_TOKEN }}
          app-url: "http://localhost:4173"
          companion-assets-folder: "companion-assets"
          companion-assets-regex: "^/assets/"

Multiple Asset Patterns

If you need to serve assets from multiple paths:

      - name: Prepare companion assets
        run: |
          mkdir -p companion-assets
          # Copy static assets
          cp -r public/static companion-assets/static
          # Copy built bundles
          cp -r dist/bundles companion-assets/bundles

      - name: Run Meticulous tests
        uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
        with:
          api-token: ${{ secrets.METICULOUS_API_TOKEN }}
          app-url: "http://localhost:3000"
          companion-assets-folder: "companion-assets"
          # Match /static/ OR /bundles/
          companion-assets-regex: "^/(static|bundles)/"

React App (Create React App)

      - name: Build React app
        run: pnpm build

      - name: Prepare companion assets
        run: |
          mkdir -p companion-assets
          # Copy static files from build output
          cp -r build/static companion-assets/static

      - name: Serve app
        run: |
          npx serve -s build -p 3000 &
          sleep 3

      - name: Run Meticulous tests
        uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
        with:
          api-token: ${{ secrets.METICULOUS_API_TOKEN }}
          app-url: "http://localhost:3000"
          companion-assets-folder: "companion-assets"
          companion-assets-regex: "^/static/"

Folder Structure Requirements

The folder structure inside companion-assets-folder must match the URL paths.

Example 1: Next.js

Request URL: http://localhost:3000/_next/static/chunks/main.js

Regex: ^/_next/static/

Folder structure:

companion-assets/
└── _next/
    └── static/
        └── chunks/
            └── main.js

The pathname /_next/static/chunks/main.js maps to companion-assets/_next/static/chunks/main.js.

Example 2: Generic Assets

Request URL: http://localhost:3000/assets/images/logo.png

Regex: ^/assets/

Folder structure:

companion-assets/
└── assets/
    └── images/
        └── logo.png

Example 3: Root-Level Assets

Request URL: http://localhost:3000/public/font.woff2

Regex: ^/public/

Folder structure:

companion-assets/
└── public/
    └── font.woff2

Regex Pattern Guide

Basic Patterns

Use CaseRegexMatches
Next.js static assets^/_next/static//_next/static/chunks/main.js<br/>/_next/static/css/app.css
Assets folder^/assets//assets/images/logo.png<br/>/assets/fonts/font.woff
Static folder^/static//static/js/bundle.js<br/>/static/css/main.css
Multiple folders`^/(assetsstatic)/`
Specific file types`^/.*.(pngjpg

Advanced Patterns

Match all .js files in /dist/:

^/dist/.*\.js$

Match versioned assets:

^/assets/v[0-9]+/

Matches: /assets/v1/main.js, /assets/v2/app.css

Match specific subdirectories:

^/static/(js|css|media)/

Matches: /static/js/main.js, /static/css/app.css, /static/media/logo.png


Troubleshooting

Assets Not Loading

Symptom: Your app shows missing resources or broken styles during tests.

Possible Causes:

  1. Regex doesn't match requests

    • Check the browser network tab in test results
    • Verify the exact pathname being requested
    • Test your regex pattern at regex101.com
    # Example: If requests are for /_next/static/chunks/main-abc123.js
    # This regex won't match (missing trailing slash):
    companion-assets-regex: "^/_next/static"
    
    # This will match:
    companion-assets-regex: "^/_next/static/"
    
  2. Folder structure doesn't match URL structure

    • Request: /_next/static/main.js
    • Correct: companion-assets/_next/static/main.js
    • Incorrect: companion-assets/static/main.js
  3. Files not copied to companion assets folder

    • Verify files exist: ls -la companion-assets/_next/static/
    • Check your build output directory
    • Ensure copy commands run after build

Wrong Files Served

Symptom: Meticulous serves incorrect or outdated files.

Solution: Ensure you're copying the correct build output.

# Bad: Copies from source instead of build output
cp -r src/assets companion-assets/

# Good: Copies from build output
cp -r .next/static companion-assets/_next/

Assets Upload Too Large

Symptom: Companion assets upload times out or fails.

Solutions:

  1. Exclude unnecessary files:

    # Only copy necessary files
    cp -r .next/static companion-assets/_next/
    # Don't copy source maps in production
    find companion-assets -name "*.map" -delete
    
  2. Use more specific regex:

    # Instead of matching all assets
    companion-assets-regex: "^/assets/"
    
    # Match only large files (images, fonts)
    companion-assets-regex: "^/assets/.*\.(png|jpg|woff2|woff|ttf)$"
    

Debugging Request Matching

To see which requests are being intercepted, you can add the --printRequests flag when using the CLI:

npx @alwaysmeticulous/cli ci run-with-tunnel \
  --apiToken="$METICULOUS_API_TOKEN" \
  --appUrl="http://localhost:3000" \
  --companionAssetsFolder="companion-assets" \
  --companionAssetsRegex="^/_next/static/" \
  --printRequests

For GitHub Actions, you can add the meticulous-debug label to your PR to keep the secure tunnel open and access detailed logs.


Performance Considerations

When Companion Assets Help

  • Large files: Images, videos, fonts (>100KB)
  • Many files: Hundreds of small assets loaded per page
  • Slow builds: Next.js apps with large static asset folders
  • Bandwidth limits: CI environments with limited egress

When Companion Assets May Not Help

  • Small apps: Apps with minimal static assets (<10MB total)
  • Fast tunnels: If tunnel performance is already good
  • Dynamic assets: Assets that change based on runtime logic

Measuring Impact

Compare test run times with and without companion assets:

  1. Run tests without companion assets
  2. Note the total test run duration
  3. Enable companion assets
  4. Compare the new duration

Typical improvements: 10-30% faster test runs for Next.js apps with large static folders.


CLI Reference

ci run-with-tunnel

npx @alwaysmeticulous/cli ci run-with-tunnel \
  --apiToken="<token>" \
  --appUrl="<url>" \
  --companionAssetsFolder="<folder>" \
  --companionAssetsRegex="<regex>"

Parameters:

  • --companionAssetsFolder: Path to local folder (relative or absolute)
  • --companionAssetsRegex: Regex pattern (must be properly escaped)

ci start-tunnel

When testing tunnel setup locally:

npx @alwaysmeticulous/cli ci start-tunnel --port=3000

GitHub Actions Reference

cloud-compute Action

- uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
  with:
    api-token: ${{ secrets.METICULOUS_API_TOKEN }}
    app-url: "http://localhost:3000"
    companion-assets-folder: "companion-assets"  # Required if regex provided
    companion-assets-regex: "^/_next/static/"    # Required if folder provided

Input Types:

  • companion-assets-folder: String (path)
  • companion-assets-regex: String (regex pattern)

Defaults: Both default to empty string (feature disabled)


Common Patterns

Monorepo with Multiple Next.js Apps

      - name: Build apps
        run: |
          pnpm build:app1
          pnpm build:app2

      - name: Prepare companion assets for both apps
        run: |
          mkdir -p companion-assets/app1/_next
          mkdir -p companion-assets/app2/_next
          cp -r apps/app1/.next/static companion-assets/app1/_next/
          cp -r apps/app2/.next/static companion-assets/app2/_next/

      - name: Run tests for App 1
        uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
        with:
          api-token: ${{ secrets.APP1_API_TOKEN }}
          app-url: "http://localhost:3000"
          companion-assets-folder: "companion-assets/app1"
          companion-assets-regex: "^/_next/static/"

      - name: Run tests for App 2
        uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
        with:
          api-token: ${{ secrets.APP2_API_TOKEN }}
          app-url: "http://localhost:4000"
          companion-assets-folder: "companion-assets/app2"
          companion-assets-regex: "^/_next/static/"

Conditional Companion Assets

      - name: Prepare companion assets (only for Next.js)
        if: ${{ env.FRAMEWORK == 'nextjs' }}
        run: |
          mkdir -p companion-assets/_next
          cp -r .next/static companion-assets/_next/

      - name: Run Meticulous tests
        uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
        with:
          api-token: ${{ secrets.METICULOUS_API_TOKEN }}
          app-url: "http://localhost:3000"
          companion-assets-folder: ${{ env.FRAMEWORK == 'nextjs' && 'companion-assets' || '' }}
          companion-assets-regex: ${{ env.FRAMEWORK == 'nextjs' && '^/_next/static/' || '' }}

Summary

Use companion assets when:

  • You have a Next.js application
  • You have large static assets that slow down the tunnel
  • Assets are served from a CDN during recording but should be local during tests
  • You need to consolidate assets from multiple servers

Configuration requirements:

  • Both companion-assets-folder and companion-assets-regex must be provided
  • Folder structure must match URL path structure
  • Regex must accurately match the requests you want to intercept

Common mistakes to avoid:

  • Mismatched folder structure and URL paths
  • Regex that doesn't match actual request paths
  • Forgetting to build the app before copying assets
  • Copying source files instead of build output