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.
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.
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.
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.
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.
When you configure companion assets, Meticulous:
- Uploads the specified local folder to cloud storage
- Starts a static file server in the test environment with your assets
- Intercepts requests matching the regex pattern
- Redirects matching requests to the local asset server instead of proxying through the tunnel
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
Companion assets require two parameters, and you must provide both or neither:
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
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.
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/tocompanion-assets/_next/(preserves path structure) - Regex
^/_next/static/matches all requests starting with/_next/static/ - Folder structure in
companion-assetsmatches 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/"
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/"
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)/"
- 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/"
The folder structure inside companion-assets-folder must match the URL paths.
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.
Request URL: http://localhost:3000/assets/images/logo.png
Regex: ^/assets/
Folder structure:
companion-assets/
└── assets/
└── images/
└── logo.png
Request URL: http://localhost:3000/public/font.woff2
Regex: ^/public/
Folder structure:
companion-assets/
└── public/
└── font.woff2
| Use Case | Regex | Matches |
|---|---|---|
| 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 | `^/(assets | static)/` |
| Specific file types | `^/.*.(png | jpg |
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
Symptom: Your app shows missing resources or broken styles during tests.
Possible Causes:
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/"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
- Request:
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
- Verify files exist:
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/
Symptom: Companion assets upload times out or fails.
Solutions:
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" -deleteUse 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)$"
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.
- 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
- 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
Compare test run times with and without companion assets:
- Run tests without companion assets
- Note the total test run duration
- Enable companion assets
- Compare the new duration
Typical improvements: 10-30% faster test runs for Next.js apps with large static folders.
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)
When testing tunnel setup locally:
npx @alwaysmeticulous/cli ci start-tunnel --port=3000
- 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)
- 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/"
- 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/' || '' }}
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-folderandcompanion-assets-regexmust 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