Create React App - Complete Setup Guide
Complete guide for setting up Meticulous with React applications built with Create React App (CRA).
Overview
Create React App is the official way to create single-page React applications. This guide covers:
- Recorder installation in
public/index.html - CI/CD configuration with static asset upload
- Authentication handling
- Common patterns and troubleshooting
Prerequisites:
- React application created with Create React App
- Basic familiarity with Meticulous concepts
Note: Create React App is no longer actively maintained. For new projects, consider using Vite or Next.js.
Quick Start
Step 1: Install Recorder in public/index.html
Add the Meticulous recorder script to your public/index.html before any other scripts.
File: public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Your app description" />
<title>Your App Name</title>
<!-- Meticulous recorder - MUST be first script -->
<!-- Replace YOUR_PROJECT_ID with your project ID from the dashboard -->
<script
data-project-id="YOUR_PROJECT_ID"
src="https://snippet.meticulous.ai/v1/meticulous.js"
></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Important: The recorder must load before React to capture all events.
Step 2: Configure GitHub Actions Workflow
CRA builds to static files, so we use the upload-assets action.
File: .github/workflows/meticulous.yml
name: Meticulous Tests
on:
push:
branches: [main]
pull_request: {}
workflow_dispatch: {}
permissions:
actions: write
contents: read
issues: write
pull-requests: write
statuses: read
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build app
run: npm run build
env:
NODE_ENV: production
CI: false # Treats warnings as non-blocking
- name: Upload and test
uses: alwaysmeticulous/report-diffs-action/upload-assets@v1
with:
api-token: ${{ secrets.METICULOUS_API_TOKEN }}
app-directory: "build"
rewrites: |
[
{ "source": "/(.*)", "destination": "/index.html" }
]
Note: CI: false prevents build from failing on warnings. Remove if you want strict builds.
Step 3: Add API Token Secret
- Get your API token from Meticulous dashboard → Project Settings
- Go to GitHub repo → Settings → Secrets and variables → Actions
- Create secret named
METICULOUS_API_TOKENwith your token
How It Works
upload-assets Action
The upload-assets action:
- Uploads your built static files to Meticulous
- Serves them on a temporary URL
- Runs tests against that URL
- Reports diffs back to your PR
Build output: CRA builds to build/ directory by default.
Rewrites Configuration
The rewrites parameter handles client-side routing:
[
{ "source": "/(.*)", "destination": "/index.html" }
]
This ensures all routes serve index.html, allowing React Router to handle routing.
Common Patterns
Pattern 1: Detect Test Mode
Use window.Meticulous.isRunningAsTest in your components:
function MyComponent() {
const isTest = window.Meticulous?.isRunningAsTest
if (isTest) {
// Skip animations, use test data, etc.
}
return <div>...</div>
}
Pattern 2: Bypass Authentication
// In src/App.js or auth provider
import { useEffect } from 'react'
function App() {
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
// Mock authentication for tests
localStorage.setItem('auth-token', 'test-token')
localStorage.setItem('user', JSON.stringify({
id: 'test-user',
name: 'Test User',
email: 'test@example.com'
}))
}
}, [])
return <YourApp />
}
Pattern 3: Handle Environment Variables
CRA uses REACT_APP_ prefix for environment variables:
const apiUrl = process.env.REACT_APP_API_URL
// Use different URL for tests
const effectiveUrl = window.Meticulous?.isRunningAsTest
? 'https://api.test.example.com'
: apiUrl
In workflow:
- name: Build app
run: npm run build
env:
REACT_APP_API_URL: "https://api.example.com"
NODE_ENV: production
CI: false
Pattern 4: Mock Service Worker for APIs
Use MSW to mock API calls:
// src/mocks/browser.js
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
// src/index.js
if (window.Meticulous?.isRunningAsTest && 'serviceWorker' in navigator) {
const { worker } = await import('./mocks/browser')
await worker.start()
}
ReactDOM.render(<App />, document.getElementById('root'))
Complete Example
File Structure
your-app/
├── public/
│ └── index.html # Recorder installation
├── src/
│ ├── index.js # Entry point
│ ├── App.js # Main app component
│ ├── components/
│ ├── lib/
│ │ └── auth.js # Auth utilities
│ └── mocks/ # MSW mocks (optional)
├── .github/
│ └── workflows/
│ └── meticulous.yml # CI/CD
├── package.json
└── .env # Environment variables
Example: App with Authentication
File: src/App.js
import { useEffect, useState } from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { checkAuth } from './lib/auth'
function App() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// Mock auth for tests
if (window.Meticulous?.isRunningAsTest) {
setUser({
id: 'test-user-123',
name: 'Test User',
email: 'test@example.com'
})
setLoading(false)
return
}
// Normal auth flow
checkAuth().then(user => {
setUser(user)
setLoading(false)
}).catch(() => {
setUser(null)
setLoading(false)
})
}, [])
if (loading) {
return (
<div className="loading">
<p>Loading...</p>
</div>
)
}
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/dashboard"
element={user ? <Dashboard user={user} /> : <Navigate to="/login" />}
/>
<Route path="/login" element={<LoginPage />} />
</Routes>
</BrowserRouter>
)
}
export default App
CI/CD Configuration Details
Environment Variables
CRA environment variables must be prefixed with REACT_APP_:
- name: Build app
run: npm run build
env:
REACT_APP_API_URL: "https://api.example.com"
REACT_APP_APP_NAME: "My App"
NODE_ENV: production
CI: false
In code:
const apiUrl = process.env.REACT_APP_API_URL
Custom Build Script
If you have a custom build script:
- name: Build app
run: npm run build:custom
env:
NODE_ENV: production
Using Yarn
If your project uses Yarn:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build app
run: yarn build
Troubleshooting
Issue: Build Fails with Warnings
Symptom: Build fails in CI with ESLint or TypeScript warnings
Cause: CRA treats warnings as errors when CI=true
Fix: Set CI=false in build step:
- name: Build app
run: npm run build
env:
CI: false
NODE_ENV: production
Alternative: Fix the warnings properly
Issue: Recorder Not Loading
Symptom: window.Meticulous is undefined
Checks:
- Verify recorder script is in
public/index.html<head> - Check project ID is correct
- Check for CSP blocking (console errors)
Fix: Ensure correct placement in public/index.html:
<head>
<!-- Recorder FIRST -->
<script data-project-id="..." src="https://snippet.meticulous.ai/v1/meticulous.js"></script>
<!-- Then other head elements -->
<title>Your App</title>
</head>
Issue: Routes Return 404
Symptom: Direct navigation to routes like /about returns 404
Cause: Missing rewrite configuration
Fix: Ensure rewrites in workflow:
rewrites: |
[
{ "source": "/(.*)", "destination": "/index.html" }
]
Issue: Environment Variables Not Working
Symptom: process.env.REACT_APP_API_URL is undefined
Common causes:
- Variable not prefixed with
REACT_APP_ - Not added to workflow
env:section - Trying to access in workflow but not in code
Fix:
# In workflow
- name: Build app
run: npm run build
env:
REACT_APP_API_URL: "https://api.example.com" # Must have REACT_APP_ prefix
// In code
const apiUrl = process.env.REACT_APP_API_URL
console.log('API URL:', apiUrl) // Should log the URL
Issue: API Calls Fail
Symptom: API requests fail during tests
Cause: No backend in upload-assets mode
Solutions:
Option 1: Mock APIs with MSW (recommended — see Pattern 4 above)
Keeps you on upload-assets, which is the simplest and most reliable workflow.
Option 2: Switch to upload-container (if you have a backend you want to actually run)
Build a Docker image that runs your backend (and optionally your frontend), and switch the workflow to upload-container:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
tags: my-app:${{ github.sha }}
platforms: linux/amd64
push: false
load: true
- name: Run Meticulous tests
uses: alwaysmeticulous/report-diffs-action/upload-container@v1
with:
api-token: ${{ secrets.METICULOUS_API_TOKEN }}
image-tag: my-app:${{ github.sha }}
container-port: 3000
Option 3: Switch to cloud-compute (last resort — keeps a server alive in CI)
- name: Start backend
run: npm run start:api &
- name: Start frontend
run: npm start &
env:
PORT: 3000
- name: Wait for app
run: npx wait-on http://localhost:3000
- name: Run Meticulous tests
uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1
with:
api-token: ${{ secrets.METICULOUS_API_TOKEN }}
app-url: "http://localhost:3000"
Issue: False Positive Diffs
Symptom: Tests show diffs for unchanged content
Common causes:
- Animations not completing
- Random keys or IDs
- Timestamps or dynamic data
Fixes:
Animations: Disable in tests
const animationDuration = window.Meticulous?.isRunningAsTest ? 0 : 300
Random IDs: Use deterministic values
const generateId = () => {
if (window.Meticulous?.isRunningAsTest) {
return 'test-id-12345'
}
return Math.random().toString(36).substr(2, 9)
}
Timestamps: Add meticulous-ignore class
<span className="meticulous-ignore">
Last updated: {new Date().toLocaleString()}
</span>
Learn more: Fix False Positive Diffs
Testing Best Practices
1. Test Build Locally
Before pushing to CI:
# Build your app
npm run build
# Serve built files
npx serve -s build
# Verify it works at http://localhost:3000
2. Handle Loading States Properly
Ensure loading states complete before rendering content:
if (loading) {
return <div className="loading">Loading...</div>
}
if (error) {
return <div className="error">Error: {error.message}</div>
}
return <div>{/* Your content */}</div>
3. Use Consistent Placeholders
Instead of spinners, use skeleton screens for consistent layouts:
if (loading) {
return <SkeletonCard />
}
Advanced Configuration
Monorepo Setup
If your CRA app is in a subdirectory:
- name: Install dependencies
working-directory: ./apps/frontend
run: npm ci
- name: Build app
working-directory: ./apps/frontend
run: npm run build
env:
CI: false
- name: Upload and test
uses: alwaysmeticulous/report-diffs-action/upload-assets@v1
with:
api-token: ${{ secrets.METICULOUS_API_TOKEN }}
app-directory: "./apps/frontend/build"
rewrites: |
[
{ "source": "/(.*)", "destination": "/index.html" }
]
Custom Public Path
If you serve your app from a subdirectory:
In package.json:
{
"homepage": "/my-app"
}
In workflow:
rewrites: |
[
{ "source": "/my-app/(.*)", "destination": "/index.html" }
]
Migration to Modern Tools
CRA is no longer actively maintained. Consider migrating to:
- Vite: Faster builds, modern tooling
- Next.js: Server-side rendering, better performance
- Remix: Full-stack framework
Migration resources:
See Also
- Onboarding Guide - General Meticulous setup
- Troubleshoot Authentication - Auth patterns and solutions
- Fix False Positives - Handle non-deterministic content
- React with Vite - Modern alternative to CRA