{"_id":"eslint-plugin-better-ssr","_rev":"5-ab57181e4d95ce4602ed9c7bc8873257","name":"eslint-plugin-better-ssr","dist-tags":{"latest":"2.1.3"},"versions":{"2.0.0":{"name":"eslint-plugin-better-ssr","version":"2.0.0","keywords":["eslint","eslintplugin","ssr","server-side-rendering","globals","react","nextjs","typescript","flat-config"],"author":{"name":"rnd332"},"license":"MIT","_id":"eslint-plugin-better-ssr@2.0.0","maintainers":[{"name":"rnd332","email":"eureq631@gmail.com"}],"homepage":"https://github.com/kopiro/eslint-plugin-ssr-friendly","bugs":{"url":"https://github.com/kopiro/eslint-plugin-ssr-friendly/issues"},"dist":{"shasum":"e186dcac09c1d2b339d7aa9684f3dff5d96aa91b","tarball":"https://registry.npmjs.org/eslint-plugin-better-ssr/-/eslint-plugin-better-ssr-2.0.0.tgz","fileCount":23,"integrity":"sha512-kK1xwdMzyR6IvDFcW30P7v+Z3rdmZP5ToPf990MHu8CttVLw8fvbchXVxUUTHGzyEukln7eNlbfqU1XbTZBQ7w==","signatures":[{"sig":"MEUCIDVTJS7pq01EuWtl5xp8UHcqpF1RbPFdglXela7NqGQNAiEAtVKctkBPkVygiX+GiuEQbl/+tklYQOveD5vDvZLM7jA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":52549},"main":"dist/index.js","type":"commonjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"gitHead":"534b502076f225cea65f7db29644c2d18b734c9d","scripts":{"lint":"eslint src/","test":"ts-node --project tsconfig.test.json test/index.test.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"rnd332","email":"eureq631@gmail.com"},"repository":{"url":"git+https://github.com/kopiro/eslint-plugin-ssr-friendly.git","type":"git"},"_npmVersion":"11.11.0","description":"Modern TypeScript fork of eslint-plugin-ssr-friendly with ESLint 9/10, flat config, and Next.js App Router support","directories":{"test":"test"},"_nodeVersion":"24.14.1","dependencies":{"globals":"^13.24.0"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","ts-node":"^10.9.2","typescript":"^5.3.0","@typescript-eslint/parser":"^8.0.0"},"peerDependencies":{"eslint":"^8.0.0 || ^9.0.0 || ^10.0.0"},"peerDependenciesMeta":{"@typescript-eslint/parser":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-better-ssr_2.0.0_1774473881973_0.955486227194744","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"eslint-plugin-better-ssr","version":"2.1.0","keywords":["eslint","eslintplugin","flat-config","globals","nextjs","react","server-side-rendering","ssr","typescript"],"author":{"name":"rnd332"},"license":"MIT","_id":"eslint-plugin-better-ssr@2.1.0","maintainers":[{"name":"rnd332","email":"eureq631@gmail.com"}],"homepage":"https://github.com/kopiro/eslint-plugin-ssr-friendly","bugs":{"url":"https://github.com/kopiro/eslint-plugin-ssr-friendly/issues"},"dist":{"shasum":"3c5698612e3826c1091b568626cc833849d6378e","tarball":"https://registry.npmjs.org/eslint-plugin-better-ssr/-/eslint-plugin-better-ssr-2.1.0.tgz","fileCount":23,"integrity":"sha512-rJh59hCqHIirF5tu2aYuLTTGkZKyxUFdQyao8gA80ECtmkb4gUg8C2MxAH90R9t06uxhpFrycIo9au3KRKfQFw==","signatures":[{"sig":"MEQCIEqjs4nCXUPgfQbGZ4ogXnpZqYbwuaSRahdGd1t5g2B/AiB6Uj3MjerNbfnEPzxRmken38AC80qFtm1MlqKSrRVGSA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":62119},"main":"dist/index.js","type":"commonjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"gitHead":"178f12c3e7b9e28fe8d4bbd652ca0ad12d6cca66","scripts":{"lint":"eslint src/","test":"ts-node --project tsconfig.test.json test/index.test.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"rnd332","email":"eureq631@gmail.com"},"repository":{"url":"git+https://github.com/kopiro/eslint-plugin-ssr-friendly.git","type":"git"},"_npmVersion":"11.11.0","description":"Modern TypeScript fork of eslint-plugin-ssr-friendly with ESLint 9/10, flat config, and Next.js App Router support","directories":{"test":"test"},"_nodeVersion":"24.14.1","dependencies":{"globals":"^13.24.0"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","ts-node":"^10.9.2","typescript":"^5.3.0","@typescript-eslint/parser":"^8.0.0"},"peerDependencies":{"eslint":"^8.0.0 || ^9.0.0 || ^10.0.0"},"peerDependenciesMeta":{"@typescript-eslint/parser":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-better-ssr_2.1.0_1774476649565_0.11984384721682018","host":"s3://npm-registry-packages-npm-production"}},"2.1.1":{"name":"eslint-plugin-better-ssr","version":"2.1.1","keywords":["eslint","eslintplugin","flat-config","globals","nextjs","react","server-side-rendering","ssr","typescript"],"author":{"name":"rnd332"},"license":"MIT","_id":"eslint-plugin-better-ssr@2.1.1","maintainers":[{"name":"rnd332","email":"eureq631@gmail.com"}],"homepage":"https://github.com/kopiro/eslint-plugin-ssr-friendly","bugs":{"url":"https://github.com/kopiro/eslint-plugin-ssr-friendly/issues"},"dist":{"shasum":"f44f165cc4b322be91d2ec4bef53cff10f5e5ddc","tarball":"https://registry.npmjs.org/eslint-plugin-better-ssr/-/eslint-plugin-better-ssr-2.1.1.tgz","fileCount":23,"integrity":"sha512-oNNpQOZKOuy6zHwF9fav8dbMzHj9P2K/udFALyi4AH1fdlZcnM+FINVKoEQZNkyl8sxJ3yA4ru1TP4imuKMXqw==","signatures":[{"sig":"MEUCIQCD5SKRvUg4M1aJPtsV5Kv5h0LJpVtVtx0CNIDaGUY6iQIgGS3sVEvYlCfNSJVpx8XWcoNSm1Cx15HK5yy5ASe7kFU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":62723},"main":"dist/index.js","type":"commonjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"gitHead":"a1f742a9f32c7c5549f246da01a4ee74f1402844","scripts":{"lint":"eslint src/","test":"ts-node --project tsconfig.test.json test/index.test.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"rnd332","email":"eureq631@gmail.com"},"repository":{"url":"git+https://github.com/kopiro/eslint-plugin-ssr-friendly.git","type":"git"},"_npmVersion":"11.11.0","description":"Modern TypeScript fork of eslint-plugin-ssr-friendly with ESLint 9/10, flat config, and Next.js App Router support","directories":{"test":"test"},"_nodeVersion":"24.14.1","dependencies":{"globals":"^13.24.0"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","ts-node":"^10.9.2","typescript":"^5.3.0","@typescript-eslint/parser":"^8.0.0"},"peerDependencies":{"eslint":"^8.0.0 || ^9.0.0 || ^10.0.0"},"peerDependenciesMeta":{"@typescript-eslint/parser":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-better-ssr_2.1.1_1774477165040_0.9147036211177402","host":"s3://npm-registry-packages-npm-production"}},"2.1.2":{"name":"eslint-plugin-better-ssr","version":"2.1.2","keywords":["eslint","eslintplugin","flat-config","globals","nextjs","react","server-side-rendering","ssr","typescript"],"author":{"name":"rnd332"},"license":"MIT","_id":"eslint-plugin-better-ssr@2.1.2","maintainers":[{"name":"rnd332","email":"eureq631@gmail.com"}],"homepage":"https://github.com/kopiro/eslint-plugin-ssr-friendly","bugs":{"url":"https://github.com/kopiro/eslint-plugin-ssr-friendly/issues"},"dist":{"shasum":"fbdcd153e69a5d3b1c9b28d2fd1a428b763e0184","tarball":"https://registry.npmjs.org/eslint-plugin-better-ssr/-/eslint-plugin-better-ssr-2.1.2.tgz","fileCount":23,"integrity":"sha512-CQ4d3PH4/bakIl5Z5xsVZmyDbdtTdief3LvkU/JSpI/Rx9CVMGccOMSDiC1u42NcYMIC+Dbh9rrV4TKP5HjpYw==","signatures":[{"sig":"MEYCIQDvuh+sAMgf9cCO5YOFLdhoYUSBtk0b7fEDOD4PbYbzMQIhAIQP15OEpwb29ew1nUu/X3ASQr8pUNHHjWf5lsi0fKdO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63579},"main":"dist/index.js","type":"commonjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"gitHead":"dfdd01217b73e0c9388d6c3478449d9c40e730e1","scripts":{"lint":"eslint src/","test":"ts-node --project tsconfig.test.json test/index.test.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"rnd332","email":"eureq631@gmail.com"},"repository":{"url":"git+https://github.com/kopiro/eslint-plugin-ssr-friendly.git","type":"git"},"_npmVersion":"11.11.0","description":"Modern TypeScript fork of eslint-plugin-ssr-friendly with ESLint 9/10, flat config, and Next.js App Router support","directories":{"test":"test"},"_nodeVersion":"24.14.1","dependencies":{"globals":"^13.24.0"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","ts-node":"^10.9.2","typescript":"^5.3.0","@typescript-eslint/parser":"^8.0.0"},"peerDependencies":{"eslint":"^8.0.0 || ^9.0.0 || ^10.0.0"},"peerDependenciesMeta":{"@typescript-eslint/parser":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-better-ssr_2.1.2_1774477687277_0.6986572759581764","host":"s3://npm-registry-packages-npm-production"}},"2.1.3":{"name":"eslint-plugin-better-ssr","version":"2.1.3","description":"Modern TypeScript fork of eslint-plugin-ssr-friendly with ESLint 9/10, flat config, and Next.js App Router support","keywords":["eslint","eslintplugin","flat-config","globals","nextjs","react","server-side-rendering","ssr","typescript"],"homepage":"https://github.com/kopiro/eslint-plugin-ssr-friendly","bugs":{"url":"https://github.com/kopiro/eslint-plugin-ssr-friendly/issues"},"license":"MIT","author":{"name":"rnd332"},"repository":{"type":"git","url":"git+https://github.com/kopiro/eslint-plugin-ssr-friendly.git"},"directories":{"test":"test"},"type":"commonjs","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"import":"./dist/index.js","require":"./dist/index.js","types":"./dist/index.d.ts"}},"scripts":{"build":"tsc","clean":"rm -rf dist","prepublishOnly":"npm run build","test":"ts-node --project tsconfig.test.json test/index.test.ts","typecheck":"tsc --noEmit","lint":"eslint src/"},"dependencies":{"globals":"^13.24.0"},"devDependencies":{"@typescript-eslint/parser":"^8.0.0","eslint":"^9.0.0","ts-node":"^10.9.2","typescript":"^5.3.0"},"peerDependencies":{"eslint":"^8.0.0 || ^9.0.0 || ^10.0.0"},"peerDependenciesMeta":{"@typescript-eslint/parser":{"optional":true}},"gitHead":"6147b44de3b00c1658466562eefa66cc53e274a3","_id":"eslint-plugin-better-ssr@2.1.3","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-8FgeAOR8cSEmPh34CK/olcPU821oJPezKmJipUF/wkU+Axd8cLrsc8RrS+P+lU6sT9G+TnVLmgfbtWc3j7NLGA==","shasum":"2086db8be6a85cd7ac896c45d4dd827aa47b2435","tarball":"https://registry.npmjs.org/eslint-plugin-better-ssr/-/eslint-plugin-better-ssr-2.1.3.tgz","fileCount":23,"unpackedSize":64425,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC7I63NBTrCPjQ+lL9hOkJiaDrZ22uqZO47ladbPL9QQgIgaLRWfTf98K1lX7dNUUitu6LggHv5yul8PYDaLcX293Y="}]},"_npmUser":{"name":"rnd332","email":"eureq631@gmail.com"},"maintainers":[{"name":"rnd332","email":"eureq631@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eslint-plugin-better-ssr_2.1.3_1774478376096_0.8027985512818945"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-25T21:24:41.972Z","modified":"2026-03-25T22:39:36.376Z","2.0.0":"2026-03-25T21:24:42.114Z","2.1.0":"2026-03-25T22:10:49.713Z","2.1.1":"2026-03-25T22:19:25.196Z","2.1.2":"2026-03-25T22:28:07.410Z","2.1.3":"2026-03-25T22:39:36.250Z"},"bugs":{"url":"https://github.com/kopiro/eslint-plugin-ssr-friendly/issues"},"author":{"name":"rnd332"},"license":"MIT","homepage":"https://github.com/kopiro/eslint-plugin-ssr-friendly","keywords":["eslint","eslintplugin","flat-config","globals","nextjs","react","server-side-rendering","ssr","typescript"],"repository":{"type":"git","url":"git+https://github.com/kopiro/eslint-plugin-ssr-friendly.git"},"description":"Modern TypeScript fork of eslint-plugin-ssr-friendly with ESLint 9/10, flat config, and Next.js App Router support","maintainers":[{"name":"rnd332","email":"eureq631@gmail.com"}],"readme":"# eslint-plugin-better-ssr\n\nModern TypeScript fork of [eslint-plugin-ssr-friendly](https://github.com/kopiro/eslint-plugin-ssr-friendly) with ESLint 8/9/10, flat config, and Next.js App Router support.\n\nDetects incorrect use of DOM globals (`window`, `document`, `navigator`, etc.) to help you write SSR-safe code that works in both Node.js and the browser.\n\n## Installation\n\n```bash\nnpm install --save-dev eslint-plugin-better-ssr\n```\n\n## Usage\n\n### Flat config (`eslint.config.js`) — ESLint 9+\n\n```js\n// eslint.config.js\nimport betterSsr from \"eslint-plugin-better-ssr\";\n\nexport default [\n  betterSsr.configs.recommended,\n  // ... your other configs\n];\n```\n\n### Legacy config (`.eslintrc`) — ESLint 8\n\n```json\n{\n  \"plugins\": [\"better-ssr\"],\n  \"extends\": [\"plugin:better-ssr/recommended\"]\n}\n```\n\n## Rules\n\n### `no-dom-globals-in-module-scope`\n\nDisallow use of DOM globals at the module or global scope. Accessing `window`, `document`, `navigator`, etc. during `import` will crash in Node.js.\n\n```js\n// ❌ Bad — crashes on the server\nconst retina = devicePixelRatio > 2;\n\n// ✅ Good — lazy evaluation\nconst getRetina = () => devicePixelRatio > 2;\n\n// ✅ Good — typeof guard\nconst width = typeof window !== \"undefined\" ? window.innerWidth : 0;\n```\n\n### `no-dom-globals-in-react`\n\nDisallow use of DOM globals in React function component bodies and SSR-unsafe hook callbacks (`useMemo`, `useCallback`, `useRef`).\n\nDOM globals **are** allowed inside:\n\n- `useEffect` / `useLayoutEffect` callbacks\n- Custom hooks (functions starting with `use`)\n- Code guarded by `typeof window === 'undefined'` / `typeof globalThis === 'undefined'` early returns\n- Files with `\"use client\"` directive (opt-in)\n\n```js\n// ❌ Bad — runs during SSR\nconst Header = () => {\n  const width = window.innerWidth;\n  return <div style={{ width }} />;\n};\n\n// ❌ Bad — useMemo runs during SSR\nconst isTouch = useMemo(() => navigator.maxTouchPoints > 0, []);\n\n// ✅ Good — useEffect only runs on the client\nconst Header = () => {\n  const [width, setWidth] = useState(0);\n  useEffect(() => {\n    setWidth(window.innerWidth);\n  }, []);\n  return <div style={{ width }} />;\n};\n\n// ✅ Good — custom hook\nfunction useDevicePixelRatio() {\n  if (typeof window !== \"undefined\") {\n    return window.devicePixelRatio;\n  }\n  return 1;\n}\n\n// ✅ Good — typeof guard with globalThis (unicorn/prefer-global-this compatible)\nconst isTouchDevice = useMemo(() => {\n  if (typeof globalThis === \"undefined\") return false;\n  return \"ontouchstart\" in globalThis;\n}, []);\n\n// ✅ Good — compound typeof guard\nconst isTouchDevice = useMemo(() => {\n  if (typeof globalThis === \"undefined\" || typeof window === \"undefined\") return false;\n  return \"ontouchstart\" in globalThis || navigator.maxTouchPoints > 0;\n}, []);\n```\n\n### Options\n\n#### `allowInUseClient`\n\nType: `boolean` (default: `false`)\n\nWhen `true`, files starting with a `\"use client\"` or `\"use server\"` directive are skipped by `no-dom-globals-in-react`:\n\n```js\n// eslint.config.js\nimport betterSsr from \"eslint-plugin-better-ssr\";\n\nexport default [\n  {\n    plugins: { \"better-ssr\": betterSsr },\n    rules: {\n      \"better-ssr/no-dom-globals-in-react\": [\n        \"error\",\n        { allowInUseClient: true },\n      ],\n    },\n  },\n];\n```\n\n## Backward Compatibility\n\nThe original FC rule name is preserved as an alias:\n\n| Old name (v1)                       | New name (v2)                    |\n| ----------------------------------- | -------------------------------- |\n| `no-dom-globals-in-module-scope`    | `no-dom-globals-in-module-scope` |\n| `no-dom-globals-in-react-fc`        | `no-dom-globals-in-react`        |\n\n## License\n\nMIT\n","readmeFilename":"README.md"}