{"id":13531626,"url":"https://github.com/pinecone-router/router","last_synced_at":"2026-01-30T04:30:33.713Z","repository":{"id":51250755,"uuid":"358402552","full_name":"pinecone-router/router","owner":"pinecone-router","description":"The extensible Alpine.js router","archived":false,"fork":false,"pushed_at":"2025-03-26T18:07:25.000Z","size":2194,"stargazers_count":272,"open_issues_count":1,"forks_count":8,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-26T19:24:57.176Z","etag":null,"topics":["alpine","alpine-components","alpine-router","alpinejs","alpinejs-router","client-side-routing","pinecone","pinecone-router","router","routing"],"latest_commit_sha":null,"homepage":"https://pinecone-example.vercel.app/","language":"TypeScript","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/pinecone-router.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":"rehhouari","tidelift":null,"community_bridge":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2021-04-15T21:55:50.000Z","updated_at":"2025-03-26T18:07:29.000Z","dependencies_parsed_at":"2023-11-30T20:29:00.186Z","dependency_job_id":"8e403713-e287-4282-a4c3-e00e70ae22bf","html_url":"https://github.com/pinecone-router/router","commit_stats":{"total_commits":157,"total_committers":1,"mean_commits":157.0,"dds":0.0,"last_synced_commit":"7eac5c39516a08e353e743b01c8d21a7061e548e"},"previous_names":["rehhouari/alpine-router"],"tags_count":52,"template":false,"template_full_name":"alpine-collective/plugin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pinecone-router%2Frouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pinecone-router%2Frouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pinecone-router%2Frouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pinecone-router%2Frouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pinecone-router","download_url":"https://codeload.github.com/pinecone-router/router/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246700717,"owners_count":20819923,"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":["alpine","alpine-components","alpine-router","alpinejs","alpinejs-router","client-side-routing","pinecone","pinecone-router","router","routing"],"created_at":"2024-08-01T07:01:04.430Z","updated_at":"2026-01-30T04:30:33.706Z","avatar_url":"https://github.com/pinecone-router.png","language":"TypeScript","funding_links":["https://ko-fi.com/rehhouari"],"categories":["Extensions \u0026 Plugins","TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"http://github.com/pinecone-router/router/raw/v7/.github/pinecone-router-social-card-alt-dark.png?raw=true\" title=\"Pinecone Router logo with the text: The feature-packed router for\n   Alpine.js.\"\u003e\n\u003c/p\u003e\n\n\u003cdiv align=\"center\" style=\"\"\u003e\n\n[![npm](https://img.shields.io/npm/dm/pinecone-router?label=npm\u0026logo=npm\u0026labelColor=%23d7f4ee\u0026color=%230b2822\u0026style=flat\u0026logoColor=%230b2822)](https://npmjs.com/package/pinecone-router)\n![jsDelivr hits (npm)](https://img.shields.io/jsdelivr/npm/hm/pinecone-router?style=flat\u0026logo=jsdelivr\u0026logoColor=%230b2822\u0026label=jsdelivr\u0026labelColor=d7f4ee\u0026color=%230b2822)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/pinecone-router@7.4.1?labelColor=%23d7f4ee\u0026style=flat\u0026color=%230b2822\u0026\u0026logo=bun\u0026logoColor=%230b2822)](https://bundlephobia.com/result?p=pinecone-router@7.4.1)\n\u003cbr\u003e\n[![Changelog](https://img.shields.io/badge/changelog-0b2822?style=flat)](./CHANGELOG.md)\n[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/pinecone-router/router?labelColor=%23d7f4ee\u0026color=%230b2822\u0026label=version\u0026style=flat\u0026sort=semver\u0026logo=semver\u0026logoColor=%230b2822)](https://github.com/pinecone-router/router/tree/7.4.1)\n[![Sponsor](https://img.shields.io/badge/sponsor-0b2822?logo=githubsponsors\u0026style=flat)](https://ko-fi.com/rehhouari)\n\n\u003c/div\u003e\n\n# Pinecone Router\n\nA small, easy to use, and feature-packed router for Alpine.js.\n\n```html\n\u003cdiv x-data=\"app\" \u003e\n  \u003ctemplate x-route=\"/\" x-template\u003e\n    \u003ch1\u003eWelcome!\u003c/h1\u003e\n    \u003cp\u003eWhat's your name?\u003c/p\u003e\n    \u003cinput @enter=\"$router.navigate('/'+$el.value)\"\u003e\u003c/input\u003e\n  \u003c/template\u003e\n\n  \u003ctemplate x-route=\"/:name\" x-handler=\"handler\" x-template\u003e\n    \u003ch1\u003eHello \u003cspan x-text=\"$params.name\"\u003e\u003c/span\u003e!\u003c/h1\u003e\n    \u003cbutton @click=\"$history.back()\"\u003eGo Back\u003c/button\u003e\n  \u003c/template\u003e\n\n  \u003ctemplate x-route=\"notfound\" x-template=\"/404.html\"\u003e\u003c/template\u003e\n\u003c/div\u003e\n\n\u003cscript\u003e\n  document.addEventListener('alpine:init', () =\u003e {\n    Alpine.data('app', () =\u003e ({\n      handler(context, controller) {\n        if (context.params.name == 'easter') {\n          this.$router.navigate('/easter-egg')\n        }\n      },\n    }))\n  })\n\u003c/script\u003e\n```\n\n## Features:\n\n- :smile: Easy and familiar syntax well integrated with Alpine.js.\n- :gear: [Handler functions](#x-handler) allow you to run functions on for each\n  route.\n- :beginner:\u0026nbsp;\u0026nbsp;[Inline](#inline-templates) and [external](#x-template)\n  templates to display content.\n- :sparkles: 3 Magic helpers to easily access\n  router data.\n- \u0026nbsp;\u003cimg src=\"https://skillicons.dev/icons?i=ts\" width=\"14px\" /\u003e\n  \u0026nbsp;Full Typescript support.\n- :link: Automatic [click handling](#bypass-click-handling) and\n  [loading events](#events--loading).\n- :hash: [Hash routing](#settings) support.\n\n**Demo**: [Pinecone example](https://pinecone-router.pages.dev/),\n[(source code)](./example/).\n\n## Installation\n\nThis projects follow the [Semantic Versioning](https://semver.org/) gui=delines.\n\n\u003e [!IMPORTANT]\n\u003e Check the [CHANGELOG](./CHANGELOG.md) before major updates.\n\n\u003e [!NOTE]\n\u003e If you're upgrading from v6, also see the more compact\n\u003e [Upgrade Guide](./upgrade_to_v7.md).\n\n### CDN\n\nInclude the following `\u003cscript\u003e` tag in the `\u003chead\u003e` of your document,\n**before Alpine.js**:\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/pinecone-router@7.4.1/dist/router.min.js\"\u003e\u003c/script\u003e\n```\n\n### NPM\n\n```\nnpm install pinecone-router\n```\n\n```javascript\nimport PineconeRouter from 'pinecone-router'\nimport Alpine from 'alpinejs'\nAlpine.plugin(PineconeRouter)\nAlpine.start()\n```\n\n### Browser Module\n\n```javascript\nimport PineconeRouter from 'https://cdn.jsdelivr.net/npm/pinecone-router@7.4.1/dist/router.esm.js'\nimport Alpine from 'https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/module.esm.js'\nAlpine.plugin(PineconeRouter)\nAlpine.start()\n```\n\n## Usage\n\n### [Demo \u0026 Usage Example](https://pinecone-router.pages.dev/)\n\n## `x-route`\n\nDeclare routes by creating a template tag with the `x-route` directive.\n\n```html\n\u003cdiv x-data=\"...\"\u003e\n\t\u003ctemplate x-route=\"/\"\u003e\u003c/template\u003e\n\t\u003ctemplate x-route=\"/hello/:name\"\u003e\u003c/template\u003e\n\t\u003ctemplate x-route=\"notfound\"\u003e\u003c/template\u003e\n\u003c/div\u003e\n```\n\n\u003e [!NOTE]\n\u003e Alternatively you can\n\u003e [use Javascript to add routes](#add--remove-routes-programmatically)\n\n\u003e [!NOTE]\n\u003e Read more: [`notfound` route](#notfound-route), [Named routes](#named-routes)\n\n### Route matching\n\n#### Segments types\n\n- **Literal** (`/literal`): Matches `/literal` but not `/something-else`\n- **Named** (`/:name`): Matches `/john` or `/123` but not `/john/doe` or `/`\n- **Optional** (`/:name?`): Matches `/profile` or `/profile/john` but\n  not `/profile/john/settings`\n- **Rest** (`/users/:rest+`): Matches `/users/john` or `/users/john/settings`\n  but not `/users` (matches one or more)\n- **Wildcard** (`/:path*`): Matches `/files`, `/files/docs`,\n  `/files/docs/report.pdf` (matches zero or more)\n- **Suffix** (`/movies/:title.mp4`): Matches `/movies/avatar.mp4` but not\n  `/movies/avatar.mov` or `/movies/avatar`\n- **Suffix Pattern** (`/movies/:title.(mp4|mov)`): Matches `/movies/avatar.mp4`\n  or `/movies/avatar.mov` but not `/movies/avatar.avi` or `/movies/avatar`\n\n\u003e [!IMPORTANT]\n\u003e Trailing slashes are normalized (both `/about` and `/about/` work the same)\n\u003e\n\u003e Matching is case-insensitive\n\n#### Accessing params\n\nYou can access the params' values with:\n\n- `$params` magic helper: `$params.paramName` from Alpine.js components.\n- `context.params.paramName` from inside handlers.\n- [`PineconeRouter.context.params`](#pineconerouter-object) from elsewhere\n  in JS.\n\n## `x-template`\n\n`x-template` allows you to display content everytime the _route changes_.\n\n### Inline templates\n\nBy adding an empty `x-template` attribute to a route template element,\nPinecone Router will render its children when the route is matched.\n\n```html\n\u003ctemplate x-route=\"/\" x-template\u003e\n\t\u003cdiv\u003eHello World!\u003c/div\u003e\n\t\u003cp\u003eWorks with multiple children as well\u003c/p\u003e\n\u003c/template\u003e\n```\n\nIn this example it will inserts the child elements into the document the same\nway `x-if` does: _after the `template` tag_.\n\n#### Modifiers\n\n- **`.target`**: `.target.app` will render the inline template inside\n  the element with the `app` ID:\n\n```html\n\u003ctemplate x-route=\"/\" x-template.target.app\u003e\n\t\u003cdiv\u003eHello World!\u003c/div\u003e\n\u003c/template\u003e\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n```\n\n\u003e [!TIP]\n\u003e Default Target ID can be set globally in [Settings](#settings).\n\n### External templates\n\n`x-template` also allows you to specify one or more external template files\nto be fetched from a URL.\n\n```html\n\u003ctemplate x-route=\"/\" x-template=\"/home.html\"\u003e\u003c/template\u003e\n\u003ctemplate\n\tx-route=\"/header\"\n\tx-template=\"['/header.html', '/home.html']\"\n\u003e\u003c/template\u003e\n```\n\nIn this example it will fetch the html files and inserts them in the document\nthe same way `x-if` does: after the appropriate `template` tags.\n\n#### Modifiers\n\n- **`.preload`**: Fetches the templates after the first page load at\n  `low` priority, without waiting for the route to be matched.\n- **`.target`**: Takes an ID paramater for example `.target.app` will render\n  the template inside the element with the `app` ID\n- **`.interpolate`**: Enable named route params in template urls.\n\n```html\n\u003c!-- you can preload templates --\u003e\n\u003ctemplate x-route=\"notfound\" x-template.preload=\"/404.html\"\u003e\u003c/template\u003e\n\n\u003c!-- you can specify an element to render into --\u003e\n\u003ctemplate\n\tx-route=\"/profile/:id\"\n\tx-template.target.app=\"/profile.html\"\n\u003e\u003c/template\u003e\n\n\u003c!-- this will fetch templates according to the current route params --\u003e\n\u003c!-- on /dyamic/foo it it will fetch /api/dynamic/foo.html, and so on --\u003e\n\u003c!-- this can be helpful when using an API that generates HTML --\u003e\n\u003ctemplate\n\tx-route=\"/dynamic/:name\"\n\tx-template.interpolate=\"/api/dynamic/:name.html\"\n\u003e\n\u003c/template\u003e\n\n\u003cdiv id=\"app\"\u003e\n\t\u003c!-- profile.html content will be displayed here --\u003e\n\u003c/div\u003e\n```\n\n\u003e [!NOTE]\n\u003e Templates's content are cached by PineconeRouter in a variable when loaded,\n\u003e and are automatically cleared on browser page reload.\n\n\u003cbr\u003e\n\n\u003e [!NOTE]\n\u003e When fetching a template fails, it adispatches a\n\u003e [`pinecone:fetch-error`](#events--loading) event to `document`.\n\n\u003e [!TIP]\n\u003e Modifiers can be used simulateneously: `x-template.preload.target.app`\n\u003e For obvious reasons, `.preload` cannot be used with `.interpolate`.\n\n\u003e [!TIP]\n\u003e Preload can be used globally in [Settings](#settings).\n\n\u003e [!TIP]\n\u003e Default Target ID can be set globally in [Settings](#settings).\n\n### Embeded Scripts\n\nTemplates can have their own script elements, which will run when the route is\nmatched.\n\n`/template.html`:\n\n```html\n\u003cdiv x-data=\"hello\" x-effect=\"effect\"\u003e\n\t\u003ch1\u003eHomepage\u003c/h1\u003e\n\t\u003cp x-text=\"message\"\u003e\u003c/p\u003e\n\u003c/div\u003e\n\u003cscript\u003e\n\tAlpine.data('hello', () =\u003e ({\n\t\tmessage: 'Hello world',\n\t\tinit() {\n\t\t\tconsole.log('hello from init()')\n\t\t},\n\t\teffect() {\n\t\t\t// this will run whenever the param `name` changes\n\t\t\tif (this.$params.name == 'world') {\n\t\t\t\tconsole.log('hello world')\n\t\t\t}\n\t\t},\n\t}))\n\u003c/script\u003e\n```\n\n\u003e [!IMPORTANT]\n\u003e Templates does _not_ re-render when the path/params changes on the same route.\n\u003e `init()` will run only once until the user visits another route then comes\n\u003e back.\n\n\u003e [!TIP]\n\u003e To run a function when params change, use `x-effect` or `$watch`:\n\n```html\n\u003cdiv x-data=\"hello\" x-effect=\"getData\"\u003e\u003c/div\u003e\n\u003cstrong x-show=\"!loading\" x-text=\"name\"\u003e\u003c/strong\u003e\n\u003cscript\u003e\n\tAlpine.data('name', () =\u003e ({\n\t\tloading: true,\n\t\tname: Alpine.$persist(''),\n\t\tasync getData() {\n\t\t\ttry {\n\t\t\t\tthis.loading = true\n\t\t\t\tconst response = await fetch(`/views/${this.$params.slug}.json`)\n\t\t\t\tconst data = await response.json()\n\t\t\t\tthis.name = data.name\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Fetch error:', error)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t}))\n\u003c/script\u003e\n```\n\n## `x-handler`\n\nThis powerful directive can be used alone or alongisde `x-template`, it allow\nyou to excute one or more methods when a route is matched.\n\n- `x-handler` takes a _function, or an array of functions_, that will be called\n  in order.\n- Handlers are awaited.\n- Handlers run **before x-template** allowing you to redirect before showing\n  them, or use handlers without `x-template `to display content from JS.\n- Handlers run before the route is added to the\n  [Navigation History](#navigation-history), so any redirection is not added to\n  the history which prevents loops.\n\n### Handler arguments\n\nEach handler function receives two arguments:\n\n1. `context` - The [HandlerContext object](#handler-type-reference) containing\n   current route information.\n2. `controller` - An\n   [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)\n   which allows you to:\n\n- check `controller.signal` to cancel your handler when a user navigates\n  elsewhere. For example, the clicked a link while handler is `fetch`ing data.\n- use `controller.abort()` to cancel subsequent handlers.\n\n### Examples\n\n```html\n\u003cdiv x-data=\"router()\" x-handler.global=\"[globalHandler]\"\u003e\n\t\u003c!-- You can pass in a function name --\u003e\n\t\u003ctemplate x-route=\"/\" x-handler=\"home\"\u003e\u003c/template\u003e\n\n\t\u003c!-- Or an anonymous/arrow function --\u003e\n\t\u003ctemplate\n\t\tx-route=\"/home\"\n\t\tx-handler=\"[(ctx) =\u003e ctx.redirect('/'), thisWontRun]\"\n\t\u003e\u003c/template\u003e\n\n\t\u003c!-- Or even an array of multiple function names/anonymous functions! --\u003e\n\t\u003ctemplate x-route=\"/hello/:name\" x-handler=\"[checkName, hello]\"\u003e\u003c/template\u003e\n\n\t\u003c!-- Handlers will be awaited, and their returned value is passed \n   to the next handler --\u003e\n\t\u003ctemplate\n\t\tx-route=\"/home\"\n\t\tx-handler=\"[awaitedHandler, processData]\"\n\t\u003e\u003c/template\u003e\n\n\t\u003c!-- 404 handler --\u003e\n\t\u003ctemplate x-route=\"notfound\" x-handler=\"notfound\"\u003e\u003c/template\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n```\n\nThe JS:\n\n```js\nfunction router() {\n\treturn {\n\t\thome(context) {\n\t\t\tdocument.querySelector('#app').innerHTML = `\u003ch1\u003eHome\u003c/h1\u003e`\n\t\t},\n\t\tcheckName(context) {\n\t\t\tif (context.params.name.toLowerCase() == 'rafik') {\n\t\t\t\talert('we have the same name!')\n\t\t\t}\n\t\t},\n\t\thello(context) {\n\t\t\tdocument.querySelector('#app').innerHTML =\n\t\t\t\t`\u003ch1\u003eHello, ${context.params.name}\u003c/h1\u003e`\n\t\t},\n\t\tnotfound(context) {\n\t\t\tdocument.querySelector('#app').innerHTML = `\u003ch1\u003eNot Found\u003c/h1\u003e`\n\t\t},\n\t\tthisWontRun(context) {\n\t\t\t// This function wont run because the previous handler redirected\n\t\t\tconsole.log('skipped!')\n\t\t},\n\t\tglobalHandler(context) {\n\t\t\t// this will be run for every router\n\t\t\tconsole.log('global handler: ', context.route)\n\t\t},\n\n\t\t// async functions will be automatically awaited by Pinecone Router\n\t\tasync awaitedHandler(ctx, controller) {\n\t\t\ttry {\n\t\t\t\t// use abort signal to cancel when the user navigates away.\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\t'https://jsonplaceholder.typicode.com/posts',\n\t\t\t\t\t{ signal: controller.signal }\n\t\t\t\t)\n\t\t\t\treturn await response.json() // pass the response to the next handler\n\t\t\t} catch (err) {\n\t\t\t\t// safely ignore aborts, but handle fetch errors\n\t\t\t\tif (err.name != 'AbortError') {\n\t\t\t\t\tconsole.error(`Download error: ${err.message}`)\n\t\t\t\t\t// abort on error for example, which wont render the route's template\n\t\t\t\t\t// nor run subsequent handlers\n\t\t\t\t\tcontroller.abort()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tprocessData(ctx) {\n\t\t\t// get previous handlers returned data\n\t\t\tif (ctx.data) {\n\t\t\t\tconsole.table(ctx.data)\n\t\t\t}\n\t\t},\n\t}\n}\n```\n\n### Modifiers\n\n- **`.global`**: define global handlers that will be run for every route, it is\n  bound to the data of the element it is defined on\n  so it's best to add to the router component element\n  (`\u003cdiv x-data=\"router\" x-handler.global=\"[]\"\u003e`), or any element with a access\n  to the handlers you're using (doesn't have to be on the same element\n  as x-data)\n  - These global handlers always run before route specific handlers.\n\n\u003e [!NOTE]\n\u003e You can also define global handlers programmatically through\n\u003e [Settings](#settings).\n\n### Prevent execution of subsequent handlers\n\nTo prevent the next handlers from executing from inside another hanlder,\nyou can:\n\n- `this.$router.navigate()` to redirect to another path, since all navigation\n  requests cancel queued handlers.\n- `controller.abort()` to cancel subsequent handlers without redirecting.\n  - This is useful if you want to show an error from a handler without\n    redirecting, ie. using JS.\n\n### Handler Type Reference\n\nThese are the types you can import if using Alpine.js with Typescript\n\n```ts\n/**\n * Handler type takes the In and Out parameters.\n *\n * @param In  is the value of the previous handler, which will be inside\n * `HandlerContext.data`.\n * @param Out is the return value of the handler.\n */\nexport type Handler\u003cIn, Out\u003e = (\n\tcontext: HandlerContext\u003cIn\u003e,\n\tcontroller: AbortController\n) =\u003e Out | Promise\u003cOut\u003e\n\nexport interface HandlerContext\u003cT = unknown\u003e extends Context {\n\treadonly data: T\n\treadonly route: Route\n}\n```\n\n## $router magic helper\n\n`$router` is a wrapper for the [PineconeRouter object](#pineconerouter-object).\n\n## PineconeRouter object\n\nYou can access the `PineconeRouter` object in a few ways:\n\n- `$router` magic helper inside Alpine.js components\n- `window.PineconeRouter` inside JS modules.\n- `PineconeRouter` inside global JS.\n- `Alpine.$router`.\n\nReference:\n\n```ts\nexport interface PineconeRouter {\n\treadonly name: string\n\treadonly version: string\n\n\troutes: RoutesMap\n\tcontext: Context\n\tsettings: (value?: Partial\u003cSettings\u003e) =\u003e Settings\n\thistory: NavigationHistory\n\n\tloading: boolean\n\n\t/**\n\t * Add a new route\n\t *\n\t * @param {string} path the path to match\n\t * @param {RouteOptions} options the options for the route\n\t */\n\tadd: (path: string, options: RouteOptions) =\u003e void\n\n\t/**\n\t * Remove a route\n\t *\n\t * @param {string} path the route to remove\n\t */\n\tremove: (path: string) =\u003e void\n\n\t/**\n\t *  Navigate to the specified path\n\t *\n\t * @param {string} path the path with no hash even if using hash routing\n\t * @param {boolean} fromPopState INTERNAL Is set to true when called from\n\t *                  onpopstate event\n\t * @param {boolean} firstLoad INTERNAL Is set to true on browser page load.\n\t * @param {number} index INTERNAL the index of the navigation history\n\t *                  that was navigated to.\n\t * @returns {Promise\u003cvoid\u003e}\n\t */\n\tnavigate: (\n\t\tpath: string,\n\t\tfromPopState?: boolean,\n\t\tfirstLoad?: boolean,\n\t\tindex?: number\n\t) =\u003e Promise\u003cvoid\u003e\n\n\t/**\n\t * Match a path against the registered routes\n\t * @param path the path to check\n\t * @returns { route: Route; params: Context['params'] }\n\t */\n\tmatch(path: string): { route: Route; params: Context['params'] }\n}\n```\n\nThe routes object is a map that has a string key which is the route path, and\na value which is a Route object.\n\n```ts\nexport type RoutesMap = Map\u003cstring, Route\u003e \u0026 {\n\tget(key: 'notfound'): Route\n}\n```\n\n\u003e [!NOTE]\n\u003e Read more: [Settings](#settings-object),\n\u003e [NavigationHistory](#navigationhistory-object), [Route](#route-object),\n\u003e [Context](#context-object),\n\u003e [RouteOptions](#route-object)\n\n## Context object\n\nContains information about the current route. This is available at all times:\n\n- Using the magic helper: `$router.context` in Alpine components\n- From Javascript using `window.PineconeRouter.context`\n- Every [`handler`](#x-handler) method takes the `context` object as the\n  first argument which you should use instead of the above.\n\nReference:\n\n```ts\n/**\n * This is the global Context object\n * Which can be accessed from `PineconeRouter.context`\n */\nexport interface Context {\n\treadonly path: string\n\treadonly route?: Route\n\treadonly params: Record\u003cstring, string | undefined\u003e\n}\n```\n\nRead more: [Route object](#route-object)\n\n## Settings:\n\nPineconeRouter can be configured using\n[`PineconeRouter.settings`](#pineconerouter-object).\n\nIn Alpine:\n\n```html\n\u003cdiv x-data=\"router\" x-init=\"$router.settings({targetID: 'app'})\"\u003e\u003c/div\u003e\n```\n\nIn JS:\n\n```html\n\u003cscript\u003e\n\tdocument.addEventListener('alpine:init', () =\u003e {\n\t\twindow.PineconeRouter.settings({\n\t\t\tbasePath: '/app',\n\t\t\ttargetID: 'app',\n\t\t})\n\t})\n\u003c/script\u003e\n```\n\n`PineconeRouter.settings()` returns the current settings.\n\n### Settings object\n\n```ts\nexport interface Settings {\n\t/**\n\t * enable hash routing\n\t * @default false: boolean\n\t */\n\thash: boolean\n\n\t/**\n\t * The base path of the site, for example /blog.\n\t * @default `/`\n\t */\n\tbasePath: string\n\n\t/**\n\t * Set an optional ID for where the templates will render by default.\n\t * This can be overridden by the .target modifier.\n\t * @default undefined\n\t */\n\ttargetID?: string\n\n\t/**\n\t * Set to false if you don't want to intercept link clicks by default.\n\t * @default true\n\t */\n\thandleClicks: boolean\n\n\t/**\n\t * Handlers that will run on every route.\n\t * @default []\n\t */\n\tglobalHandlers: Handler\u003cunknown, unknown\u003e[]\n\n\t/**\n\t * Set to true to preload all templates.\n\t * @default false\n\t * */\n\tpreload: boolean\n\n\t/**\n\t * The options object to be passed to fetch requests (second argument)\n\t * excluding `priority` which is set by the router.\n\t */\n\tfetchOptions: RequestInit\n\n\t/**\n\t * Set to false to disable calling history.pushState().\n\t * This means that the url wont change when navigating.\n\t * @default true\n\t */\n\tpushState: boolean\n}\n```\n\nRead more: [Base Path](#base-path)\n\n## Route object\n\n```ts\nexport interface Route {\n\t/**\n\t * The regex pattern used to match the route.\n\t * @internal\n\t */\n\treadonly pattern: RegExp\n\n\t/**\n\t * The raw route path\n\t */\n\treadonly path: string\n\n\t/**\n\t * The name of the route\n\t */\n\treadonly name: string\n\n\tmatch(path: string): undefined | { [key: string]: string }\n\thandlers: Handler\u003cunknown, unknown\u003e[]\n\ttemplates: string[]\n}\n\nexport interface RouteOptions {\n\thandlers?: Route['handlers']\n\tinterpolate?: boolean\n\ttemplates?: string[]\n\ttargetID?: string\n\tpreload?: boolean\n\tname?: string\n}\n```\n\n## Navigation History\n\nBesides updating the browser history, Pinecone Router also has its own\nindependent navigation [history object](#navigationhistory-object), keeping\ntrack of path visits, and allowing you to do `back()` and `forward()`\noperations without relying on the browser API.\n\nThe way it works is by recording all paths visited, excluding:\n\n- **Duplicates**; meaning if you're on '/home' and you click a link that goes\n  to '/home', it wont affect the history.\n- **Redirects**: if you're on `/home` and you visit `/profile/old` which has a\n  handler that redirects you to` /profile/new`, it will not add `/profile/old`\n  to the history, only /profile/new.\n\nIf you click a link after using `back()`, meaning the `history.index`\nis not `history.entries.length-1`, it will remove all elements\nfrom `entries` starting\nfrom the `history.index` to the end, then appends the current path.\n\n### NavigationHistory object\n\nTo access the NavigationHistory object you can use\n\n- The `$history` magic helper.\n- [PineconeRouter.history](#pineconerouter-object).\n\n```ts\nexport interface NavigationHistory {\n\t/**\n\t * The current history index\n\t */\n\tindex: number\n\n\t/**\n\t * The list of history entries\n\t */\n\tentries: string[]\n\n\t/**\n\t * Check if the router can navigate backward\n\t * @returns {boolean} true if the router can go back\n\t */\n\tcanGoBack: () =\u003e boolean\n\n\t/**\n\t * Go back to the previous route in the navigation history\n\t */\n\tback: () =\u003e void\n\n\t/**\n\t * Check if the router can navigate forward\n\t *\n\t * @returns {boolean} true if the router can go forward\n\t */\n\tcanGoForward: () =\u003e boolean\n\n\t/**\n\t * Go to the next route in the navigation history\n\t */\n\tforward: () =\u003e void\n\n\t/**\n\t * Navigate to a specific position in the navigation history\n\t *\n\t * @param index The index of the navigation position to navigate to\n\t * @returns void\n\t */\n\tto: (index: number) =\u003e void\n}\n```\n\n\u003e [!TIP]\n\u003e Use [`PineconeRouter.canGoBack()`](#pineconerouter-object) or\n\u003e [`PineconeRouter.canGoForward()`](#pineconerouter-object) to check if the\n\u003e operation is possible, for example to disable the appropriate buttons.\n\n## Others\n\n### `notfound` route\n\nBy default when PineconeRouter initializes, a default `notfound` route\nis created with the handler:\n\n```js\n;(ctx) =\u003e console.error(new ReferenceError(ROUTE_NOT_FOUND(ctx.path)))\n```\n\nYou can create a new `template` element using `x-route=\"notfound\"`\nwith`x-template` and or `x-handler` to add templates and replace the defaul\nhandler.\n\nYou can also update the `notfound` route [programmatically](#adding-a-template),\nusing [`PineconeRouter.add`](#pineconerouter-object), to which `notfound` is\nthe only expection that wont throw an error due to an exisitng route.\n\n### Named routes\n\nYou can add an optional name to the route which can be helpful in certain\nsituations:\n\n- With x-route:\n\n```html\n\u003ctemplate x-route:name=\"/test\"\u003e\u003c/template\u003e\n```\n\n- With JS:\n\n```js\nPineconeRouter.add('/test', { name: 'name' })\n```\n\n- Access inside handlers:\n\n```js\nfunction handler(context, controller) {\n\tconsole.log('route name:', context.route.name) // route name: name\n}\n```\n\n\u003e [!NOTE]\n\u003e If there was no route name suplied, it will fallback to the route's path.\n\u003e\n\u003e Names don't have to be unique.\n\n### Base Path\n\nAfter setting a [`Settings.basePath`](#settings), it will automatically added to\n`x-route` \u0026 `x-template` paths, `PineconeRouter.add()`, and to very navigation\nrequest, be it link clicks or `navigate()` calls.\n\nThis means if you set the `basePath` to `/parent`, you can now just write:\n\n- `x-route=\"/about\"` rather than `x-route=\"/parent/about\"`.\n- `x-template=\"/views/home.html\"` rather than\n  `x-template=\"/parent/views/home.html\"`.\n- `$router.navigate('/about')` rather than `$router.navigate('/parent/about')`\n\n\u003e [!NOTE]\n\u003e When using hash routing, basePath will only be added to templates urls.\n\u003e Which makes sense because hash routing don't care about the pathname\n\u003e only the hash.\n\n### Bypass click handling\n\nBy default Pinecone Router intercept all clicks on anchor elements with\n[valid attribues](./src/links.ts).\n\nAdding a `native` / `data-native` attribute to a link will prevent Pinecone\nRouter from handling it:\n\n```html\n\u003ca href=\"/foo\" native\u003eThis will be handled by the browser\u003c/a\u003e\n```\n\n### Disable click handling globally\n\nYou can set [`Settings.handleClicks`](#settings) to false to disable\nautomatically handling links by the router, unless an `x-link` attribute is\nset on the anchor element.\n\nWhen disabeld:\n\n```html\n\u003ca href=\"/path\"\u003eThis will reload the page\u003c/a\u003e\n\u003ca href=\"/path\" x-link\u003eThis won't reload the page\u003c/a\u003e\n```\n\n### Events / Loading\n\n| name                     | recipient | when it is dispatched               |\n| ------------------------ | --------- | ----------------------------------- |\n| **pinecone:start**       | document  | loading starts                      |\n| **pinecone:end**         | document  | loading ends                        |\n| **pinecone:fetch-error** | document  | fetching of external templates fail |\n\nUsage from Alpine.js:\n\n```html\n\u003cdiv @pinecone:start.document=\"\"\u003e\u003c/div\u003e\n```\n\n\u003e [!TIP]\n\u003e You can easily use [nProgress](http://ricostacruz.com/nprogress) with\n\u003e `x-template`:\n\n```js\ndocument.addEventListener('pinecone:start', () =\u003e NProgress.start())\ndocument.addEventListener('pinecone:end', () =\u003e NProgress.done())\ndocument.addEventListener('pinecone:fetch-error', (err) =\u003e console.error(err))\n```\n\n\u003e [!TIP]\n\u003e You can also use [$router.loading](#pineconerouter-object) to check the\n\u003e loading state reactively.\n\n### Add \u0026 Remove Routes Programmatically\n\nyou can add routes \u0026 remove them anytime programmatically using Javascript.\n\n#### Adding a route\n\n```js\nwindow.PineconeRouter.add(path, options)\n```\n\n- path: string, the route's path.\n- options: RouteOptions, array of route options:\n\nSee [RouteOptions](#route-object)\n\nNote that by adding handlers this way you wont have access to the `this` of the\nalpine.js component if the handler is part of one.\n\n#### Adding a template\n\nYou must add a local targetID in options or set a global one in\n[Settings](#settings):\n\n```html\n\u003cscript\u003e\n\tdocument.addEventListener('alpine:init', () =\u003e {\n\t\twindow.PineconeRouter.settings({\n\t\t\ttargetID: 'app',\n\t\t\tfetchOptions: {},\n\t\t})\n\t\twindow.PineconeRouter.add('/route', {\n\t\t\ttemplates: ['/header.html', '/body.html'],\n\t\t})\n\t\twindow.PineconeRouter.add('notfound', {\n\t\t\ttemplates: ['/404.html'],\n\t\t})\n\t})\n\u003c/script\u003e\n```\n\n\u003e [!NOTE]\n\u003e As of version 7.4.1, the templates added through this method will be added\n\u003e automatically as template elements with an x-template directive and appended\n\u003e to the end of thebody tag. this means they will function the same way as those\n\u003e added in the declarative way. ie. they will be hidden automatically\n\u003e when switching to another route.\n\n\u003e [!NOTE]\n\u003e if no targetID is provided through settings or route options, the template\n\u003e will be rendered at the bottom of the body tag, after the created template\n\u003e tag\n\n#### Removing a route\n\n[`PineconeRouter.remove(path)`](#pineconerouter-object)\n\n**Navigating from Javascript**:\n\nTo navigate to another page from javascript you can use:\n\n[`PineconeRouter.navigate(path)`](#pineconerouter-object)\n\n## Compatibility\n\n| Version | Alpine.js Version |\n| ------- | ----------------- |\n| ^v2.x   | v3                |\n| v1.x    | v2                |\n\n## Contributing:\n\nPlease refer to [CONTRIBUTING.md](/CONTRIBUTING.md)\n\n## Credits\n\n[regexparam](https://github.com/lukeed/regexparam) for new route matching logic\n\n[@shaun/alpinejs-router](https://github.com/shaunlee/alpinejs-router) for\nthe new x-if inspired [template logic](./src/templates.ts)\n\nClick handling intially method from\n[page.js](https://github.com/visionmedia/page.js/blob/master/index.js#L345).\n\n## Acknowledgment\n\n[@KevinBatdorf](https://twitter.com/KevinBatdorf) for many ideas and early\nfeedback!\n\n[Let’s code a client side router for your frameworkless SPA](https://medium.com/swlh/lets-code-a-client-side-router-for-your-no-framework-spa-19da93105e10)\nteaching client-side routing basic concepts.\n\n[@shaun/alpinejs-router](https://github.com/shaunlee/alpinejs-router/) for\nbeing a reference of how things can be done differently.\n\nLast but not least, everyone opening issues,discussions, and pull requests\nwith bug reports and feature requests!\n\n## License\n\nCopyright (c) 2021-2025 Rafik El Hadi Houari and contributors\n\nLicensed under the MIT license, see [LICENSE.md](LICENSE.md) for details.\n\n\u003ca rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"\u003e\u003cimg alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-sa/4.0/88x31.png\" /\u003e\u003c/a\u003e\u003cbr /\u003e\u003cspan xmlns:dct=\"http://purl.org/dc/terms/\" href=\"http://purl.org/dc/dcmitype/StillImage\" property=\"dct:title\" rel=\"dct:type\"\u003ePinecone Router \u003ca href=\"https://github.com/pinecone-router/logo\"\u003eLogo\u003c/a\u003e\u003c/span\u003e by \u003ca xmlns:cc=\"http://creativecommons.org/ns#\" href=\"https://rehhouari.eu.org\" property=\"cc:attributionName\" rel=\"cc:attributionURL\"\u003eRafik El Hadi Houari\u003c/a\u003e is licensed under a \u003ca rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"\u003eCreative Commons Attribution-ShareAlike 4.0 International License\u003c/a\u003e.\n\n\u003e Code from [Page.js](https://github.com/visionmedia/page.js#license) is licensed under the MIT License.\n\u003e Copyright (c) 2012 TJ Holowaychuk \u003ctj@vision-media.ca\u003e\n\n\u003e Code from [@shaun/alpinejs-router](https://github.com/shaunlee/alpinejs-router/) is licensed under the MIT License.\n\u003e Copyright (c) 2022 Shaun Li\n\n\u003e Code from [regexparam](https://github.com/lukeed/regexparam) is licensed is licensed under the MIT License.\n\u003e Copyright (c) Luke Edwards\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpinecone-router%2Frouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpinecone-router%2Frouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpinecone-router%2Frouter/lists"}