{"id":22425034,"url":"https://github.com/ceteio/next-layout-loader","last_synced_at":"2025-08-01T08:32:46.080Z","repository":{"id":45207321,"uuid":"440735154","full_name":"ceteio/next-layout-loader","owner":"ceteio","description":"File-system based nested layouts for next.js","archived":false,"fork":false,"pushed_at":"2022-05-04T02:27:54.000Z","size":114,"stargazers_count":29,"open_issues_count":3,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-26T13:20:55.257Z","etag":null,"topics":["layout","nextjs","react"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ceteio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-22T04:46:25.000Z","updated_at":"2022-11-04T04:42:32.000Z","dependencies_parsed_at":"2022-09-02T12:10:09.629Z","dependency_job_id":null,"html_url":"https://github.com/ceteio/next-layout-loader","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceteio%2Fnext-layout-loader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceteio%2Fnext-layout-loader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceteio%2Fnext-layout-loader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceteio%2Fnext-layout-loader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ceteio","download_url":"https://codeload.github.com/ceteio/next-layout-loader/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228356406,"owners_count":17907191,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["layout","nextjs","react"],"created_at":"2024-12-05T19:12:07.617Z","updated_at":"2024-12-05T19:12:08.757Z","avatar_url":"https://github.com/ceteio.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003eNext Layout Loader\u003c/h1\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003cp\u003e\n    File-system based nested layouts for next.js\n  \u003c/p\u003e\n  \u003cp\u003e\n    \u003csup\u003e\n      \u003ca href=\"https://githubbox.com/ceteio/next-layout-loader/tree/main/example\" target=\"_blank\"\u003eTry it on Codesandbox\u003c/a\u003e\n    \u003c/sup\u003e\n  \u003c/p\u003e\n    \u003cbr\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n\u003c/div\u003e\n\n```\nyarn add @ceteio/next-layout-loader\n```\n\n## Usage\n\nAdd `_layout.tsx`* files in your `pages/` directory:\n\n```\npages\n├── _app.tsx\n├── _layout.tsx\n├── index.tsx\n└── dashboard\n    ├── _layout.tsx\n    └── user\n        ├── _layout.tsx\n        └── index.tsx\n```\n\n_\u003csup\u003e* (Supports `.tsx`, `.ts`, `.jsx`, `.js`, or [any\ncustom filename with the `layoutFilenames` option](#optionslayoutfilenames))\u003c/sup\u003e_\n\nFor example:\n\n```javascript\n// pages/_layout.tsx\nimport { useState } from \"react\";\n\n// children is the file-system based component as rendered by next.js\nexport default function Layout({ children }) {\n  // State is maintained between client-side route changes!\n  const [count, setCount] = useState(0);\n  return (\n    \u003cdiv style={{ border: \"1px solid gray\", padding: \"1rem\" }}\u003e\n      \u003cp\u003e\n        \u003ccode\u003epages/_layout\u003c/code\u003e\n        \u003cbutton onClick={() =\u003e setCount(count + 1)}\u003eCount: {count}\u003c/button\u003e\n      \u003c/p\u003e\n      {children}\n    \u003c/div\u003e\n  );\n}\n\n// To hide this layout component from the router / build pipeline\nexport const getStaticProps = async () =\u003e ({ notFound: true });\n```\n\nNext, add some one-time boilerplate to `_app` (_powered by\n[`preval`](https://github.com/kentcdodds/babel-plugin-preval) \u0026\n[`codegen`](https://github.com/kentcdodds/babel-plugin-codegen)_):\n\n\u003c!-- prettier-ignore --\u003e\n```javascript\n// pages/_app.jsx\nconst filename = preval`module.exports = __filename`;\nconst withLayoutLoader = codegen.require(\"@ceteio/next-layout-loader\", filename);\n\n// Automatically renders _layout files appropriate for the current route\nexport default withLayoutLoader(({ Component, pageProps }) =\u003e (\n  \u003cComponent {...pageProps} /\u003e\n));\n```\n\nNow load your pages to see the layouts automatically applied!\n\n## Setup\n\nInstall all the dependencies:\n\n```\nyarn add @ceteio/next-layout-loader\nyarn add babel-plugin-codegen@4.1.5 babel-plugin-preval\nyarn add patch-package postinstall-postinstall\n```\n\nThe usage of [`preval`](https://github.com/kentcdodds/babel-plugin-preval) \u0026\n[`codegen`](https://github.com/kentcdodds/babel-plugin-codegen) necessitates\nusing `babel`, and hence opting-out of `swc` _(if you know how to do codegen in\n`swc`, please let me know in\n[#1](https://github.com/ceteio/next-layout-loader/issues/1)!)_. To ensure the\nlayout files are loaded correctly, you must include the `codegen` and `preval`\nplugins:\n\n`.babelrc`\n\n```json\n{\n  \"presets\": [\"next/babel\"],\n  \"plugins\": [\"codegen\", \"preval\"]\n}\n```\n\nA patch is necessary for `babel-plugin-codegen` to correctly import the\n`@ceteio/next-layout-loader` module:\n\n`package.json`\n\n```json\n{\n  \"scripts\": {\n    \"postinstall\": \"patch-package\"\n  }\n}\n```\n\nAnd create a new file `patches/babel-plugin-codegen+4.1.5.patch`:\n\n```\ndiff --git a/node_modules/babel-plugin-codegen/dist/helpers.js b/node_modules/babel-plugin-codegen/dist/helpers.js\nindex e292c8a..472d128 100644\n--- a/node_modules/babel-plugin-codegen/dist/helpers.js\n+++ b/node_modules/babel-plugin-codegen/dist/helpers.js\n@@ -99,9 +99,8 @@ function resolveModuleContents({\n   filename,\n   module\n }) {\n-  const resolvedPath = _path.default.resolve(_path.default.dirname(filename), module);\n-\n-  const code = _fs.default.readFileSync(require.resolve(resolvedPath));\n+  const resolvedPath = require.resolve(module, { paths: [_path.default.dirname(filename)] })\n+  const code = _fs.default.readFileSync(resolvedPath);\n\n   return {\n     code,\n```\n\nThen re-run `yarn`.\n\n## Configuration\n\n```\ncodegen.require(\"@ceteio/next-layout-loader\", \u003cfilename\u003e[, options])\n```\n\n### `\u003cfilename\u003e`\n\nAbsolute path to the current page file.\n\nIn the simplest case, this can be hard-coded, but wouldn't work on a different\ncomputer, or if you were to move your source files around. Instead, we use\n`preval` \u0026 `__filename` to automatically generate the correct path for us:\n\n\u003c!-- prettier-ignore --\u003e\n```javascript\nconst filename = preval`module.exports = __filename`;\nconst withLayoutLoader = codegen.require(\"@ceteio/next-layout-loader\", filename);\n```\n\n_(NOTE: This must remain as 2 separate lines. If you know how to minimise this\nboilerplate, please see\n[#2](https://github.com/ceteio/next-layout-loader/issues/2)_).\n\n### `options`\n\nAn object of further options to affect how the library loads layout files.\n\n```javascript\ncodegen.require(\"@ceteio/next-layout-loader\", filename, {\n  layoutFilenames\n});\n```\n\n#### `options.layoutFilenames`\n\n_Default_: `['_layout.tsx', '_layout.ts', '_layout.jsx', '_layout.js']`\n\nThe possible variations of layout file names within `pages/`. Can be overridden\nto use any name or extension you like.\n\n## How it works\n\nThe easiest way to understand with an example:\n\n```\npages\n├── index.tsx\n├── _app.tsx\n├── _layout.tsx\n└── dashboard\n    ├── _layout.tsx\n    └── user\n        ├── index.tsx\n        └── _layout.tsx\n```\n\n`pages/_app.tsx`:\n\n```javascript\nconst filename = preval`module.exports = __filename`;\nconst withLayoutLoader = codegen.require(\n  \"@ceteio/next-layout-loader\",\n  filename\n);\n\n// Automatically renders _layout files appropriate for the current route\nexport default withLayoutLoader(({ Component, pageProps }) =\u003e (\n  \u003cComponent {...pageProps} /\u003e\n));\n```\n\n`pages/dashboard/user/index.tsx`:\n\n```javascript\nexport default function User() {\n  return \u003ch1\u003eHello world\u003c/h1\u003e;\n}\n```\n\n`next-layout-loader` will transform the `pages/app.tsx` into:\n\n```javascript\nimport dynamic from \"next/dynamic\";\nimport { Fragment } from \"react\";\n\n// A map of directories to their layout components (if they exist)\nconst layoutMap = {\n  \"/\": __dynamic(() =\u003e import(\"./_layout.jsx\")),\n  dashboard: __dynamic(() =\u003e import(\"./dashboard/_layout.jsx\")),\n  \"dashboard/user\": __dynamic(() =\u003e import(\"./dashboard/user/_layout.jsx\"))\n};\n\nconst withLayoutLoader = wrappedFn =\u003e context =\u003e {\n  const { pageProps, router } = context;\n\n  const renderedComponent = wrappedFn(context);\n\n  return ({ Component, pageProps, router }) =\u003e {\n    const Layout1 = layoutMap[\"/\"];\n    const Layout2 = layoutMap[\"dashboard\"];\n    const Layout3 = layoutMap[\"dashboard/user\"];\n\n    return (\n      \u003cLayout1 {...pageProps}\u003e\n        \u003cLayout2 {...pageProps}\u003e\n          \u003cLayout3 {...pageProps}\u003e\n            {renderedComponent}\n          \u003c/Layout3\u003e\n        \u003c/Layout2\u003e\n      \u003c/Layout1\u003e\n    );\n  };\n})();\n\nexport default withLayoutLoader(({ Component, pageProps }) =\u003e (\n  \u003cComponent {...pageProps} /\u003e\n));\n```\n\n_\u003csup\u003e(Note: The above is a simplification; the real code has some extra logic to\nhandle all routes and their layouts)\u003c/sup\u003e_\n\n## Frequently Asked Questions\n\n### Why does this exist?\n\nThis library started as Proof Of Concept based on [a\ndiscussion](https://github.com/vercel/next.js/discussions/26389#discussioncomment-922493)\nin the Next.js repo, but it turned out to work quite well and match my mental\nmodel of how nested layouts should work. So I turned it into a library that\nanyone can use.\n\n### Why is an extra layout being applied?\n\nAn extra layout component can be unexpectedly rendered when you have the\nfollowing situation:\n\n```\npages\n├── _layout.tsx\n├── user.tsx\n└── user\n    └── _layout.tsx\n```\n\nVisiting `/user` may will render both `pages/_layout.tsx` _and_\n`pages/user/_layout.tsx`. This may not be expected (the later is in a child\ndirectory after all!), and is due to a difference in the way Next.js handles\nrendering pages vs how `@ceteio/next-layout-loader` loads layouts.\n\nTo work around this, move `pages/user.tsx` to `pages/user/index.tsx`:\n\n```diff\n pages\n ├── _layout.tsx\n-├── user.tsx\n └── user\n+    ├── index.tsx\n     └── _layout.tsx\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceteio%2Fnext-layout-loader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fceteio%2Fnext-layout-loader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceteio%2Fnext-layout-loader/lists"}