{"id":15011744,"url":"https://github.com/preactjs/preact-iso","last_synced_at":"2025-05-15T22:12:06.062Z","repository":{"id":198626119,"uuid":"701176663","full_name":"preactjs/preact-iso","owner":"preactjs","description":"Isomorphic utilities for Preact","archived":false,"fork":false,"pushed_at":"2025-04-10T03:34:11.000Z","size":357,"stargazers_count":145,"open_issues_count":6,"forks_count":14,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-08T00:39:42.940Z","etag":null,"topics":[],"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/preactjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null},"funding":{"github":["preactjs"],"open_collective":"preact"}},"created_at":"2023-10-06T05:04:31.000Z","updated_at":"2025-04-30T04:07:33.000Z","dependencies_parsed_at":null,"dependency_job_id":"4994be0a-f78f-496d-b07c-5a5baf473743","html_url":"https://github.com/preactjs/preact-iso","commit_stats":{"total_commits":154,"total_committers":18,"mean_commits":8.555555555555555,"dds":0.8246753246753247,"last_synced_commit":"d25dd0765f51c05ac7963f0248dbb81376ea1052"},"previous_names":["preactjs/preact-iso"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/preactjs%2Fpreact-iso","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/preactjs%2Fpreact-iso/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/preactjs%2Fpreact-iso/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/preactjs%2Fpreact-iso/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/preactjs","download_url":"https://codeload.github.com/preactjs/preact-iso/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254430335,"owners_count":22069909,"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":[],"created_at":"2024-09-24T19:41:35.555Z","updated_at":"2025-05-15T22:12:06.006Z","avatar_url":"https://github.com/preactjs.png","language":"JavaScript","funding_links":["https://github.com/sponsors/preactjs","https://opencollective.com/preact"],"categories":["JavaScript"],"sub_categories":[],"readme":"# preact-iso\n\n[![Preact Slack Community](https://img.shields.io/badge/slack-Preact%20Slack%20Community-blue?logo=slack)](https://chat.preactjs.com/)\n\nIsomorphic async tools for Preact.\n\n  - Lazy-load components using `lazy()` and `\u003cErrorBoundary\u003e`, which also enables progressive hydration.\n  - Generate static HTML for your app using `prerender()`, waiting for `lazy()` components and data dependencies.\n  - Implement async-aware client and server-side routing using `\u003cRouter\u003e`, including seamless async transitions.\n\n## Routing\n\n`preact-iso` offers a simple router for Preact with conventional and hooks-based APIs. The `\u003cRouter\u003e` component is async-aware: when transitioning from one route to another, if the incoming route suspends (throws a Promise), the outgoing route is preserved until the new one becomes ready.\n\n```js\nimport { lazy, LocationProvider, ErrorBoundary, Router, Route } from 'preact-iso';\n\n// Synchronous\nimport Home from './routes/home.js';\n\n// Asynchronous (throws a promise)\nconst Profiles = lazy(() =\u003e import('./routes/profiles.js'));\nconst Profile = lazy(() =\u003e import('./routes/profile.js'));\nconst NotFound = lazy(() =\u003e import('./routes/_404.js'));\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cErrorBoundary\u003e\n\t\t\t\u003cRouter\u003e\n\t\t\t\t\u003cHome path=\"/\" /\u003e\n\t\t\t\t{/* Alternative dedicated route component for better TS support */}\n\t\t\t\t\u003cRoute path=\"/profiles\" component={Profiles} /\u003e\n\t\t\t\t\u003cRoute path=\"/profile/:id\" component={Profile} /\u003e\n\t\t\t\t{/* `default` prop indicates a fallback route. Useful for 404 pages */}\n\t\t\t\t\u003cNotFound default /\u003e\n\t\t\t\u003c/Router\u003e\n\t\t\u003c/ErrorBoundary\u003e\n\t\u003c/LocationProvider\u003e\n);\n```\n\n**Progressive Hydration:** When the app is hydrated on the client, the route (`Home` or `Profile` in this case) suspends. This causes hydration for that part of the page to be deferred until the route's `import()` is resolved, at which point that part of the page automatically finishes hydrating.\n\n**Seamless Routing:** When switching between routes on the client, the Router is aware of asynchronous dependencies in routes. Instead of clearing the current route and showing a loading spinner while waiting for the next route, the router preserves the current route in-place until the incoming route has finished loading, then they are swapped.\n\n## Prerendering\n\n`prerender()` renders a Virtual DOM tree to an HTML string using [`preact-render-to-string`](https://github.com/preactjs/preact-render-to-string). The Promise returned from `prerender()` resolves to an Object with `html` and `links[]` properties. The `html` property contains your pre-rendered static HTML markup, and `links` is an Array of any non-external URL strings found in links on the generated page.\n\nPrimarily meant for use with prerendering via [`@preact/preset-vite`](https://github.com/preactjs/preset-vite#prerendering-configuration) or other prerendering systems that share the API. If you're server-side rendering your app via any other method, you can use `preact-render-to-string` (specifically `renderToStringAsync()`) directly.\n\n```js\nimport { LocationProvider, ErrorBoundary, Router, lazy, prerender as ssr } from 'preact-iso';\n\n// Asynchronous (throws a promise)\nconst Foo = lazy(() =\u003e import('./foo.js'));\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cErrorBoundary\u003e\n\t\t\t\u003cRouter\u003e\n\t\t\t\t\u003cFoo path=\"/\" /\u003e\n\t\t\t\u003c/Router\u003e\n\t\t\u003c/ErrorBoundary\u003e\n\t\u003c/LocationProvider\u003e\n);\n\nhydrate(\u003cApp /\u003e);\n\nexport async function prerender(data) {\n\treturn await ssr(\u003cApp /\u003e);\n}\n```\n\n## Nested Routing\n\nNested routes are supported by using multiple `Router` components. Partially matched routes end with a wildcard (`/*`) and the remaining value will be passed to continue matching with if there are any further routes.\n\n```js\nimport { lazy, LocationProvider, ErrorBoundary, Router, Route } from 'preact-iso';\n\nconst NotFound = lazy(() =\u003e import('./routes/_404.js'));\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cErrorBoundary\u003e\n\t\t\t\u003cRouter\u003e\n\t\t\t\t\u003cRoute path=\"/movies/*\" component={Movies} /\u003e\n\t\t\t\t\u003cNotFound default /\u003e\n\t\t\t\u003c/Router\u003e\n\t\t\u003c/ErrorBoundary\u003e\n\t\u003c/LocationProvider\u003e\n);\n\nconst TrendingMovies = lazy(() =\u003e import('./routes/movies/trending.js'));\nconst SearchMovies = lazy(() =\u003e import('./routes/movies/search.js'));\nconst MovieDetails = lazy(() =\u003e import('./routes/movies/details.js'));\n\nconst Movies = () =\u003e (\n\t\u003cErrorBoundary\u003e\n\t\t\u003cRouter\u003e\n\t\t\t\u003cRoute path=\"/trending\" component={TrendingMovies} /\u003e\n\t\t\t\u003cRoute path=\"/search\" component={SearchMovies} /\u003e\n\t\t\t\u003cRoute path=\"/:id\" component={MovieDetails} /\u003e\n\t\t\u003c/Router\u003e\n\t\u003c/ErrorBoundary\u003e\n);\n```\n\nThis will match the following routes:\n  - `/movies/trending`\n  - `/movies/search`\n  - `/movies/Inception`\n\n---\n\n## API Docs\n\n### `LocationProvider`\n\nA context provider that provides the current location to its children. This is required for the router to function.\n\nProps:\n\n  - `scope?: string | RegExp` - Sets a scope for the paths that the router will handle (intercept). If a path does not match the scope, either by starting with the provided string or matching the RegExp, the router will ignore it and default browser navigation will apply.\n\nTypically, you would wrap your entire app in this provider:\n\n```js\nimport { LocationProvider } from 'preact-iso';\n\nconst App = () =\u003e (\n    \u003cLocationProvider scope=\"/app\"\u003e\n        {/* Your app here */}\n    \u003c/LocationProvider\u003e\n);\n```\n\n### `Router`\n\nProps:\n\n  - `onRouteChange?: (url: string) =\u003e void` - Callback to be called when a route changes.\n  - `onLoadStart?: (url: string) =\u003e void` - Callback to be called when a route starts loading (i.e., if it suspends). This will not be called before navigations to sync routes or subsequent navigations to async routes.\n  - `onLoadEnd?: (url: string) =\u003e void` - Callback to be called after a route finishes loading (i.e., if it suspends). This will not be called after navigations to sync routes or subsequent navigations to async routes.\n\n```js\nimport { LocationProvider, Router } from 'preact-iso';\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cRouter\n\t\t\tonRouteChange={(url) =\u003e console.log('Route changed to', url)}\n\t\t\tonLoadStart={(url) =\u003e console.log('Starting to load', url)}\n\t\t\tonLoadEnd={(url) =\u003e console.log('Finished loading', url)}\n\t\t\u003e\n\t\t\t\u003cHome path=\"/\" /\u003e\n\t\t\t\u003cProfiles path=\"/profiles\" /\u003e\n\t\t\t\u003cProfile path=\"/profile/:id\" /\u003e\n\t\t\u003c/Router\u003e\n\t\u003c/LocationProvider\u003e\n);\n```\n\n### `Route`\n\n\nThere are two ways to define routes using `preact-iso`:\n\n1. Append router params to the route components directly: `\u003cHome path=\"/\" /\u003e`\n2. Use the `Route` component instead: `\u003cRoute path=\"/\" component={Home} /\u003e`\n\nAppending arbitrary props to components not unreasonable in JavaScript, as JS is a dynamic language that's perfectly happy to support dynamic \u0026 arbitrary interfaces. However, TypeScript, which many of us use even when writing JS (via TS's language server), is not exactly a fan of this sort of interface design.\n\nTS does not (yet) allow for overriding a child's props from the parent component so we cannot, for instance, define `\u003cHome\u003e` as taking no props _unless_ it's a child of a `\u003cRouter\u003e`, in which case it can have a `path` prop. This leaves us with a bit of a dilemma: either we define all of our routes as taking `path` props so we don't see TS errors when writing `\u003cHome path=\"/\" /\u003e` or we create wrapper components to handle the route definitions.\n\nWhile `\u003cHome path=\"/\" /\u003e` is completely equivalent to `\u003cRoute path=\"/\" component={Home} /\u003e`, TS users may find the latter preferable.\n\n```js\nimport { LocationProvider, Router, Route } from 'preact-iso';\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cRouter\u003e\n\t\t\t{/* Both of these are equivalent */}\n\t\t\t\u003cHome path=\"/\" /\u003e\n\t\t\t\u003cRoute path=\"/\" component={Home} /\u003e\n\n\t\t\t\u003cProfiles path=\"/profiles\" /\u003e\n\t\t\t\u003cProfile path=\"/profile/:id\" /\u003e\n\t\t\t\u003cNotFound default /\u003e\n\t\t\u003c/Router\u003e\n\t\u003c/LocationProvider\u003e\n);\n```\n\nProps for any route component:\n\n  - `path: string` - The path to match (read on)\n  - `default?: boolean` - If set, this route is a fallback/default route to be used when nothing else matches\n\nSpecific to the `Route` component:\n\n  - `component: AnyComponent` - The component to render when the route matches\n\n#### Path Segment Matching\n\nPaths are matched using a simple string matching algorithm. The following features may be used:\n\n  - `:param` - Matches any URL segment, binding the value to the label (can later extract this value from `useRoute()`)\n    - `/profile/:id` will match `/profile/123` and `/profile/abc`\n    - `/profile/:id?` will match `/profile` and `/profile/123`\n    - `/profile/:id*` will match `/profile`, `/profile/123`, and `/profile/123/abc`\n    - `/profile/:id+` will match `/profile/123`, `/profile/123/abc`\n  - `*` - Matches one or more URL segments\n    - `/profile/*` will match `/profile/123`, `/profile/123/abc`, etc.\n\nThese can then be composed to create more complex routes:\n\n  - `/profile/:id/*` will match `/profile/123/abc`, `/profile/123/abc/def`, etc.\n\nThe difference between `/:id*` and `/:id/*` is that in the former, the `id` param will include the entire path after it, while in the latter, the `id` is just the single path segment.\n\n  - `/profile/:id*`, with `/profile/123/abc`\n    - `id` is `123/abc`\n  - `/profile/:id/*`, with `/profile/123/abc`\n    - `id` is `123`\n\n### `useLocation`\n\nA hook to work with the `LocationProvider` to access location context.\n\nReturns an object with the following properties:\n\n  - `url: string` - The current path \u0026 search params\n  - `path: string` - The current path\n  - `query: Record\u003cstring, string\u003e` - The current query string parameters (`/profile?name=John` -\u003e `{ name: 'John' }`)\n  - `route: (url: string, replace?: boolean) =\u003e void` - A function to programmatically navigate to a new route. The `replace` param can optionally be used to overwrite history, navigating them away without keeping the current location in the history stack.\n\n### `useRoute`\n\nA hook to access current route information. Unlike `useLocation`, this hook only works within `\u003cRouter\u003e` components.\n\nReturns an object with the following properties:\n\n  - `path: string` - The current path\n  - `query: Record\u003cstring, string\u003e` - The current query string parameters (`/profile?name=John` -\u003e `{ name: 'John' }`)\n  - `params: Record\u003cstring, string\u003e` - The current route parameters (`/profile/:id` -\u003e `{ id: '123' }`)\n\n### `lazy`\n\nMake a lazily-loaded version of a Component.\n\n`lazy()` takes an async function that resolves to a Component, and returns a wrapper version of that Component. The wrapper component can be rendered right away, even though the component is only loaded the first time it is rendered.\n\n```js\nimport { lazy, LocationProvider, Router } from 'preact-iso';\n\n// Synchronous, not code-splitted:\nimport Home from './routes/home.js';\n\n// Asynchronous, code-splitted:\nconst Profiles = lazy(() =\u003e import('./routes/profiles.js').then(m =\u003e m.Profiles)); // Expects a named export called `Profiles`\nconst Profile = lazy(() =\u003e import('./routes/profile.js')); // Expects a default export\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cRouter\u003e\n\t\t\t\u003cHome path=\"/\" /\u003e\n\t\t\t\u003cProfiles path=\"/profiles\" /\u003e\n\t\t\t\u003cProfile path=\"/profile/:id\" /\u003e\n\t\t\u003c/Router\u003e\n\t\u003c/LocationProvider\u003e\n);\n```\n\nThe result of `lazy()` also exposes a `preload()` method that can be used to load the component before it's needed for rendering. Entirely optional, but can be useful on focus, mouse over, etc. to start loading the component a bit earlier than it otherwise would be.\n\n```js\nconst Profile = lazy(() =\u003e import('./routes/profile.js'));\n\nfunction Home() {\n    return (\n        \u003ca href=\"/profile/rschristian\" onMouseOver={() =\u003e Profile.preload()}\u003e\n            Profile Page -- Hover over me to preload the module!\n        \u003c/a\u003e\n    );\n}\n```\n\n### `ErrorBoundary`\n\nA simple component to catch errors in the component tree below it.\n\nProps:\n\n  - `onError?: (error: Error) =\u003e void` - A callback to be called when an error is caught\n\n```js\nimport { LocationProvider, ErrorBoundary, Router } from 'preact-iso';\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cErrorBoundary onError={(e) =\u003e console.log(e)}\u003e\n\t\t\t\u003cRouter\u003e\n\t\t\t\t\u003cHome path=\"/\" /\u003e\n\t\t\t\t\u003cProfiles path=\"/profiles\" /\u003e\n\t\t\t\t\u003cProfile path=\"/profile/:id\" /\u003e\n\t\t\t\u003c/Router\u003e\n\t\t\u003c/ErrorBoundary\u003e\n\t\u003c/LocationProvider\u003e\n);\n```\n\n### `hydrate`\n\nA thin wrapper around Preact's `hydrate` export, it switches between hydrating and rendering the provided element, depending on whether the current page has been prerendered. Additionally, it checks to ensure it's running in a browser context before attempting any rendering, making it a no-op during SSR.\n\nPairs with the `prerender()` function.\n\nParams:\n\n  - `jsx: ComponentChild` - The JSX element or component to render\n  - `parent?: Element | Document | ShadowRoot | DocumentFragment` - The parent element to render into. Defaults to `document.body` if not provided.\n\n```js\nimport { hydrate } from 'preact-iso';\n\nconst App = () =\u003e (\n\t\u003cdiv class=\"app\"\u003e\n\t\t\u003ch1\u003eHello World\u003c/h1\u003e\n\t\u003c/div\u003e\n);\n\nhydrate(\u003cApp /\u003e);\n```\n\nHowever, it is just a simple utility method. By no means is it essential to use, you can always use Preact's `hydrate` export directly.\n\n### `prerender`\n\nRenders a Virtual DOM tree to an HTML string using `preact-render-to-string`. The Promise returned from `prerender()` resolves to an Object with `html` and `links[]` properties. The `html` property contains your pre-rendered static HTML markup, and `links` is an Array of any non-external URL strings found in links on the generated page.\n\nPairs primarily with [`@preact/preset-vite`](https://github.com/preactjs/preset-vite#prerendering-configuration)'s prerendering.\n\nParams:\n\n  - `jsx: ComponentChild` - The JSX element or component to render\n\n```js\nimport { LocationProvider, ErrorBoundary, Router, lazy, prerender } from 'preact-iso';\n\n// Asynchronous (throws a promise)\nconst Foo = lazy(() =\u003e import('./foo.js'));\nconst Bar = lazy(() =\u003e import('./bar.js'));\n\nconst App = () =\u003e (\n\t\u003cLocationProvider\u003e\n\t\t\u003cErrorBoundary\u003e\n\t\t\t\u003cRouter\u003e\n\t\t\t\t\u003cFoo path=\"/\" /\u003e\n\t\t\t\t\u003cBar path=\"/bar\" /\u003e\n\t\t\t\u003c/Router\u003e\n\t\t\u003c/ErrorBoundary\u003e\n\t\u003c/LocationProvider\u003e\n);\n\nconst { html, links } = await prerender(\u003cApp /\u003e);\n```\n\n## License\n\n[MIT](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpreactjs%2Fpreact-iso","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpreactjs%2Fpreact-iso","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpreactjs%2Fpreact-iso/lists"}