{"_id":"react-native-lighthouse","name":"react-native-lighthouse","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"react-native-lighthouse","version":"1.0.0","description":"Core Web Vitals performance measurement for React Native - measure TTFF, TTI, FID and calculate Lighthouse-style performance scores","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","source":"src/index.ts","sideEffects":false,"scripts":{"build":"tsup src/index.ts --format cjs,esm --dts --clean","dev":"tsup src/index.ts --format cjs,esm --dts --watch","lint":"eslint src --ext .ts,.tsx","typecheck":"tsc --noEmit","test":"jest","test:watch":"jest --watch","prepublishOnly":"npm run build"},"keywords":["react-native","performance","lighthouse","core-web-vitals","TTFF","TTI","FID","time-to-interactive","time-to-first-frame","first-input-delay","metrics","monitoring"],"author":{"name":"Indeed Engineering"},"license":"MIT","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"repository":{"type":"git","url":"git+https://github.com/indeedeng/react-native-lighthouse.git"},"bugs":{"url":"https://github.com/indeedeng/react-native-lighthouse/issues"},"homepage":"https://github.com/indeedeng/react-native-lighthouse#readme","peerDependencies":{"react":">=17.0.0","react-native":">=0.64.0"},"devDependencies":{"@react-native/babel-preset":"^0.73.0","@testing-library/react-native":"^12.4.3","@types/jest":"^29.5.11","@types/react":"^18.2.45","@types/react-native":"^0.73.0","eslint":"^8.56.0","jest":"^29.7.0","react":"18.2.0","react-native":"^0.73.0","react-test-renderer":"18.2.0","tsup":"^8.0.1","typescript":"^5.3.3"},"_id":"react-native-lighthouse@1.0.0","gitHead":"38fec2f9f0aa31190b72654d7c713a5f861d9de5","_nodeVersion":"20.20.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-X3uOm/2ybfgqp10/1NPaCP1T/CR/vrYCFdw8CCjSmw2NKfuTjOl/jFm0/F3IbBq+vIRSUQF5tPwaEKqQ/bgP1A==","shasum":"e77b3a31fca9634d066bf239050c4c8d9277515c","tarball":"https://registry.npmjs.org/react-native-lighthouse/-/react-native-lighthouse-1.0.0.tgz","fileCount":13,"unpackedSize":87908,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDgZYSUwauyGPY/LvWxU87bkmj9p/Tz8uYmBRJe+bAFPQIge8AST6n1gdQ4Nu8N35yJqUILShp/D9i5WoV4mJOQuFM="}]},"_npmUser":{"name":"bencripps","email":"bencripps1@gmail.com"},"directories":{},"maintainers":[{"name":"bencripps","email":"bencripps1@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-native-lighthouse_1.0.0_1771617735061_0.7123262740169563"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-20T20:02:14.958Z","1.0.0":"2026-02-20T20:02:15.220Z","modified":"2026-02-20T20:02:15.425Z"},"maintainers":[{"name":"bencripps","email":"bencripps1@gmail.com"}],"description":"Core Web Vitals performance measurement for React Native - measure TTFF, TTI, FID and calculate Lighthouse-style performance scores","homepage":"https://github.com/indeedeng/react-native-lighthouse#readme","keywords":["react-native","performance","lighthouse","core-web-vitals","TTFF","TTI","FID","time-to-interactive","time-to-first-frame","first-input-delay","metrics","monitoring"],"repository":{"type":"git","url":"git+https://github.com/indeedeng/react-native-lighthouse.git"},"author":{"name":"Indeed Engineering"},"bugs":{"url":"https://github.com/indeedeng/react-native-lighthouse/issues"},"license":"MIT","readme":"# react-native-lighthouse 🏠\n\n**Core Web Vitals performance measurement for React Native.** Get Lighthouse-style performance scores for your mobile app components.\n\n## 🤔 The Challenge\n\nIndeed had traditionally been a web company. Site speed wasn't just a nice-to-have — it was fundamental to how we built systems. We believed good software was always fast, and for many years now, we had relied on Lighthouse to keep us honest. \n\nIn the past we’ve written in depth on this topic, but as we’ve transitioned to a Mobile App first company, we needed a way of bringing the same performance rigor to our native code. \n\nAs React Native proliferated across our most critical pages— ViewJob, SERP, Homepage — we found ourselves flying blind. We had no standardized way to measure whether our mobile performance was improving, degrading, or holding steady. We needed answers to fundamental questions: How fast did our screens load? When could users actually interact with them? Were we maintaining the performance standards that Indeed was known for?\n\n## 💡 The Solution: Core Web Vitals for React Native\n\nRather than reinvent the wheel, we looked to the industry standards that had proven effective on the web: Core Web Vitals. These metrics — designed by Google to capture the essence of user experience — translated remarkably well to mobile apps. We just needed to adapt them for React Native's unique threading model and lifecycle.\n\n\n## ✨ Features\n\n- 📊 **Three Core Metrics** — TTFF, TTI, and FID (mapped from web Core Web Vitals)\n- 🎯 **Lighthouse-Style Scoring** — 0-100 performance score with category ratings\n- 🪝 **Simple Hook API** — Drop-in integration with any React Native component\n- 📱 **Mobile-Optimized Thresholds** — Stricter than web, calibrated for native apps\n- 🔧 **Zero Dependencies** — Only requires React Native (no external packages)\n- 📈 **Analytics Ready** — Easy integration with any analytics service\n\n## 📦 Installation\n\n```bash\nnpm install react-native-lighthouse\n# or\nyarn add react-native-lighthouse\n# or\npnpm add react-native-lighthouse\n```\n\n## 🚀 Quick Start\n\n```tsx\nimport { usePerformanceMeasurement } from 'react-native-lighthouse';\n\nfunction ProductScreen({ productId }) {\n  const { markInteractive, panResponder, score } = usePerformanceMeasurement({\n    componentName: 'ProductScreen',\n    onReport: (metrics, score) => {\n      // Send to your analytics\n      analytics.track('screen_performance', {\n        screen: 'ProductScreen',\n        ttff: metrics.timeToFirstFrameMs,\n        tti: metrics.timeToInteractiveMs,\n        fid: metrics.firstInputDelay?.firstInputDelayMs,\n        score: score.overall,\n      });\n    },\n  });\n\n  const [product, setProduct] = useState(null);\n\n  useEffect(() => {\n    fetchProduct(productId).then(setProduct);\n  }, [productId]);\n\n  // Mark interactive when data is loaded\n  useEffect(() => {\n    if (product) {\n      markInteractive();\n    }\n  }, [product, markInteractive]);\n\n  return (\n    <View {...panResponder.panHandlers}>\n      {product ? <ProductContent product={product} /> : <Loading />}\n    </View>\n  );\n}\n```\n\n## 📊 Metrics Explained\n\n### Time to First Frame (TTFF)\n**When users first see content** — Maps to LCP (Largest Contentful Paint)\n\nMeasured from component mount to when the first frame is rendered. This tells you how quickly users see something on screen.\n\n### Time to Interactive (TTI)\n**When users can interact** — The most critical metric for mobile apps\n\nMeasured from component mount to when you call `markInteractive()`. Call this when your component is ready for user interaction (data loaded, UI ready).\n\n### First Input Delay (FID)\n**Input responsiveness** — Maps to INP/TBT (Interaction to Next Paint)\n\nMeasured automatically when users first touch the screen. Uses PanResponder to capture the delay between user input and when processing begins.\n\n## 🎯 Performance Thresholds\n\nThese are **aspirational thresholds** designed for high-performance native apps. They are stricter than official platform guidelines and based on human perception research.\n\n| Metric | Good | Needs Improvement | Poor |\n|--------|------|-------------------|------|\n| TTFF | < 300ms | 300-800ms | > 800ms |\n| TTI | < 500ms | 500-1500ms | > 1500ms |\n| FID | < 50ms | 50-150ms | > 150ms |\n\n### Threshold Rationale\n\n#### TTFF (< 300ms good)\nBased on [Jakob Nielsen's response time research](https://www.nngroup.com/articles/response-times-3-important-limits/), **100ms feels instantaneous** to users. Our 300ms \"good\" threshold provides buffer while staying well under the 1-second limit where users lose their flow of thought. There is no official native mobile standard for component-level render times.\n\n#### TTI (< 500ms good)  \nDerived from Google's app startup guidelines. [Google Play considers cold starts > 5 seconds as \"bad behavior\"](https://support.google.com/googleplay/android-developer/answer/9844486), with industry best practice targeting < 2 seconds. For individual components (assuming ~4 major components per screen), 500ms keeps total screen TTI under 2 seconds.\n\n#### FID (< 50ms good)\nWell-supported by academic research. Studies show users can [perceive touch latency as low as 5-10ms](https://dl.acm.org/doi/10.1145/2556288.2557037) during drag operations, and commercial devices currently have 50-200ms latency. Our 50ms threshold aligns with the upper bound of imperceptible delay.\n\n### Comparison to Official Standards\n\n| Source | Metric | Threshold |\n|--------|--------|-----------|\n| **Google Play (Android Vitals)** | Cold start \"bad\" | > 5 seconds |\n| **Google Play** | Warm start \"bad\" | > 2 seconds |\n| **Google Play** | Frozen frame | > 700ms |\n| **Web Core Web Vitals** | LCP good | < 2,500ms |\n| **Web Core Web Vitals** | INP good | < 200ms |\n| **Jakob Nielsen** | \"Instantaneous\" | < 100ms |\n| **Jakob Nielsen** | \"Flow maintained\" | < 1,000ms |\n\nOur thresholds are intentionally stricter because:\n- ✅ Native apps have pre-bundled code (no network fetch for JS/HTML)\n- ✅ No parsing overhead (unlike web browsers)\n- ✅ Users expect native apps to feel faster than web\n- ✅ Component-level measurement (not full app startup)\n\n### Custom Thresholds\n\nIf the default thresholds don't fit your use case, you can provide your own:\n\n```ts\nimport { calculatePerformanceScore } from 'react-native-lighthouse';\n\n// More lenient thresholds aligned with Google's app startup guidelines\nconst relaxedThresholds = {\n  ttff: { good: 1000, poor: 3000 },\n  tti: { good: 2000, poor: 5000 },\n  fid: { good: 100, poor: 300 },\n};\n\nconst score = calculatePerformanceScore(metrics, relaxedThresholds);\n```\n\n## 📈 Scoring System\n\nMetrics are combined into a single 0-100 score using weighted averages:\n\n| Metric | Weight | Rationale |\n|--------|--------|-----------|\n| TTI | 45% | Mobile users expect immediate interactivity |\n| FID | 30% | Touch interactions must feel instant |\n| TTFF | 25% | Visual feedback matters but less than interactivity |\n\n### Score Categories\n\n| Score | Category | Meaning |\n|-------|----------|---------|\n| 90-100 | Excellent | Exceptional performance |\n| 75-89 | Good | Solid performance |\n| 50-74 | Needs Improvement | Noticeable issues |\n| 0-49 | Poor | Significant problems |\n\n## 📖 API Reference\n\n### `usePerformanceMeasurement(options)`\n\nMain hook for measuring component performance.\n\n#### Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `componentName` | `string` | **required** | Name for identification in logs |\n| `namespace` | `string` | `undefined` | Group prefix (e.g., 'checkout', 'profile') |\n| `fidTimeout` | `number` | `5000` | Ms to wait for FID before logging |\n| `debug` | `boolean` | `__DEV__` | Enable console logging |\n| `onMetricsReady` | `function` | `undefined` | Called when metrics update |\n| `onInteractive` | `function` | `undefined` | Called when markInteractive() is called |\n| `onReport` | `function` | `undefined` | Called with final metrics and score |\n\n#### Returns\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `markInteractive` | `() => void` | Call when component is ready for interaction |\n| `metrics` | `PerformanceMetrics \\| null` | Current performance metrics |\n| `panResponder` | `PanResponder` | Attach to root View for FID measurement |\n| `score` | `PerformanceScore \\| null` | Current Lighthouse-style score |\n\n### `calculatePerformanceScore(metrics, thresholds?, weights?)`\n\nCalculate a performance score from metrics.\n\n```ts\nimport { calculatePerformanceScore } from 'react-native-lighthouse';\n\nconst score = calculatePerformanceScore({\n  timeToFirstFrameMs: 250,\n  timeToInteractiveMs: 400,\n  mountStartTimeMs: 1000,\n  firstFrameTimeMs: 1250,\n});\n\nconsole.log(score);\n// { overall: 95, breakdown: { ttff: 100, tti: 100, fid: 100 }, category: 'excellent' }\n```\n\n\n## 🧩 Examples\n\n### Basic Usage\n\n```tsx\nfunction HomeScreen() {\n  const { markInteractive, panResponder } = usePerformanceMeasurement({\n    componentName: 'HomeScreen',\n  });\n\n  const [data, setData] = useState(null);\n\n  useEffect(() => {\n    loadHomeData().then((data) => {\n      setData(data);\n      markInteractive();\n    });\n  }, [markInteractive]);\n\n  return (\n    <ScrollView {...panResponder.panHandlers}>\n      {data ? <HomeContent data={data} /> : <Skeleton />}\n    </ScrollView>\n  );\n}\n```\n\n### With Analytics Integration\n\n```tsx\nfunction CheckoutScreen() {\n  const { markInteractive, panResponder, score } = usePerformanceMeasurement({\n    componentName: 'CheckoutScreen',\n    namespace: 'checkout',\n    onReport: (metrics, score) => {\n      // Amplitude\n      amplitude.track('screen_performance', {\n        screen_name: 'checkout',\n        ttff_ms: metrics.timeToFirstFrameMs,\n        tti_ms: metrics.timeToInteractiveMs,\n        fid_ms: metrics.firstInputDelay?.firstInputDelayMs ?? null,\n        score: score.overall,\n        category: score.category,\n      });\n\n      // Or Firebase\n      analytics().logEvent('performance', {\n        component: 'CheckoutScreen',\n        score: score.overall,\n      });\n    },\n  });\n\n  // ... rest of component\n}\n```\n\n### Conditional Interactivity\n\n```tsx\nfunction SearchResults({ query }) {\n  const { markInteractive, panResponder } = usePerformanceMeasurement({\n    componentName: 'SearchResults',\n  });\n\n  const [results, setResults] = useState(null);\n  const [isLoading, setIsLoading] = useState(true);\n\n  useEffect(() => {\n    setIsLoading(true);\n    searchAPI(query)\n      .then(setResults)\n      .finally(() => setIsLoading(false));\n  }, [query]);\n\n  // Mark interactive only when we have results and loading is complete\n  useEffect(() => {\n    if (!isLoading && results) {\n      markInteractive();\n    }\n  }, [isLoading, results, markInteractive]);\n\n  return (\n    <FlatList\n      {...panResponder.panHandlers}\n      data={results}\n      renderItem={({ item }) => <ResultItem item={item} />}\n      ListEmptyComponent={isLoading ? <Loading /> : <NoResults />}\n    />\n  );\n}\n```\n\n### Multiple Components\n\nTrack performance for nested components:\n\n```tsx\nfunction ProductDetailScreen() {\n  const { markInteractive: markScreenInteractive, panResponder } = usePerformanceMeasurement({\n    componentName: 'ProductDetailScreen',\n    namespace: 'product',\n  });\n\n  return (\n    <ScrollView {...panResponder.panHandlers}>\n      <ProductHeader />\n      <ProductGallery />\n      <ProductActions onReady={markScreenInteractive} />\n    </ScrollView>\n  );\n}\n\nfunction ProductActions({ onReady }) {\n  const { markInteractive } = usePerformanceMeasurement({\n    componentName: 'ProductActions',\n    namespace: 'product',\n    onInteractive: onReady, // Chain to parent\n  });\n\n  const [inventory, setInventory] = useState(null);\n\n  useEffect(() => {\n    checkInventory().then((data) => {\n      setInventory(data);\n      markInteractive();\n    });\n  }, [markInteractive]);\n\n  return <ActionButtons inventory={inventory} />;\n}\n```\n\n## 🔧 TypeScript\n\nFull TypeScript support with exported types:\n\n```ts\nimport type {\n  PerformanceMetrics,\n  PerformanceScore,\n  PerformanceHookResult,\n  UsePerformanceMeasurementOptions,\n  PerformanceThresholds,\n  MetricWeights,\n} from 'react-native-lighthouse';\n```\n\n## 🤝 Contributing\n\nContributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## 📄 License\n\nMIT © [Your Name]\n\n---\n\n<p align=\"center\">\n  <sub>Built with ❤️ for the React Native community</sub>\n</p>\n","readmeFilename":"README.md","_rev":"1-2322f95ae7fe0b7cda4f27e5cde5b653"}