{"id":21866425,"url":"https://github.com/yesmeck/safe-routes","last_synced_at":"2025-04-13T16:48:29.122Z","repository":{"id":45115757,"uuid":"437561521","full_name":"yesmeck/safe-routes","owner":"yesmeck","description":"Typesafe routing for React Router apps.","archived":false,"fork":false,"pushed_at":"2024-12-11T18:29:25.000Z","size":469,"stargazers_count":439,"open_issues_count":6,"forks_count":21,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-12-19T09:05:39.116Z","etag":null,"topics":["react-router","remix","typescript"],"latest_commit_sha":null,"homepage":"","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/yesmeck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2021-12-12T14:02:48.000Z","updated_at":"2024-12-19T00:05:12.000Z","dependencies_parsed_at":"2024-11-14T09:31:34.285Z","dependency_job_id":"feb4760b-39e8-46a5-af88-160f9bfd788f","html_url":"https://github.com/yesmeck/safe-routes","commit_stats":{"total_commits":166,"total_committers":12,"mean_commits":"13.833333333333334","dds":0.2349397590361446,"last_synced_commit":"30f5e24c7c4e15a9479780ead58d483389c506eb"},"previous_names":["yesmeck/safe-routes","yesmeck/remix-routes"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesmeck%2Fsafe-routes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesmeck%2Fsafe-routes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesmeck%2Fsafe-routes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesmeck%2Fsafe-routes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yesmeck","download_url":"https://codeload.github.com/yesmeck/safe-routes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248749334,"owners_count":21155677,"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":["react-router","remix","typescript"],"created_at":"2024-11-28T05:05:56.035Z","updated_at":"2025-04-13T16:48:29.101Z","avatar_url":"https://github.com/yesmeck.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# safe-routes\n\nType-safe helper for manipulating internal links in your React Router apps.\n\n\u003e [!note]\n\u003e remix-routes has been renamed to safe-routes. If you are looking for the documentation of remix-routes, please refer to [here](https://github.com/yesmeck/remix-routes/tree/remix-routes).\n\u003e\n\u003e Please refer to the [upgrading guide](https://github.com/yesmeck/safe-routes/releases/tag/v1.0.0) if you are upgrading from remix-routes.\n\n## Highlights\n\n- [Typed URL generation](#typed-url-generation)\n- [Appending query string](#appending-query-string)\n- [Typed query string](#typed-query-string)\n- [Typed route ids](#typed-route-ids)\n- [Basename support](#basename-support)\n\n## Installation\n\n```bash\n$ npm add safe-routes\n```\n\n## Setup\n\nAdd `safeRoutes` plugin to your `vite.config.ts`:\n\n```javascript\nimport { defineConfig } from \"vite\";\nimport { reactRouter } from \"@react-router/dev/vite\";\nimport { safeRoutes } from 'safe-routes/vite';\n\nexport default defineConfig({\n  plugins: [\n    reactRouter(),\n    safeRoutes(),\n  ],\n});\n```\n\nSupported config options:\n\n- `strict: boolean`\n- `outDir: string`\n\nAdd `safe-routes typegen` to the `typecheck` script:\n\n```patch\n- \"typecheck\": \"react-router typegen \u0026\u0026 tsc --build --noEmit\"\n+ \"typecheck\": \"react-router typegen \u0026\u0026 safe-routes typegen \u0026\u0026 tsc --build --noEmit\"\n```\n\n## Usage\n\n### Typed URL generation\n\n```typescript\nimport { redirect } from 'react-router';\nimport { $path } from 'safe-routes'; // \u003c-- Import magical $path helper from safe-routes.\n\nexport const action = async ({ request }) =\u003e {\n  let formData = await request.formData();\n  const post = await createPost(formData);\n\n  return redirect($path('/posts/:id', { id: post.id })); // \u003c-- It's type safe.\n};\n```\n\n### Appending query string\n\n```typescript\nimport { $path } from 'safe-routes';\n\n$path('/posts/:id', { id: 6 }, { version: 18 }); // =\u003e /posts/6?version=18\n$path('/posts', { limit: 10 }); // =\u003e /posts?limit=10\n// You can pass any URLSearchParams init as param\n$path('/posts/delete', [['id', 1], ['id', 2]]); // =\u003e /posts/delete?id=1\u0026id=2\n```\n\n### Typed query string\n\nDefine type of query string by exporting a type named `SearchParams` in route file:\n\n```typescript\n// app/routes/posts.tsx\n\nexport type SearchParams = {\n  view: 'list' | 'grid',\n  sort?: 'date' | 'views',\n  page?: number,\n}\n```\n\n```typescript\nimport { $path } from 'safe-routes';\n\n// The query string is type-safe.\n$path('/posts', { view: 'list', sort: 'date', page: 1 });\n```\n\nYou can combine this feature with [zod](https://github.com/colinhacks/zod) and [remix-params-helper](https://github.com/kiliman/remix-params-helper) to add runtime params checking:\n\n```typescript\nimport { z } from \"zod\";\nimport { getSearchParams } from \"remix-params-helper\";\n\nconst SearchParamsSchema = z.object({\n  view: z.enum([\"list\", \"grid\"]),\n  sort: z.enum([\"price\", \"size\"]).optional(),\n  page: z.number().int().optional(),\n})\n\nexport type SearchParams = z.infer\u003ctypeof SearchParamsSchema\u003e;\n\nexport const loader = async (request) =\u003e {\n  const result = getSearchParams(request, SearchParamsSchema)\n  if (!result.success) {\n    return json(result.errors, { status: 400 })\n  }\n  const { view, sort, page } = result.data;\n}\n```\n\n### Checking params\n\n\u003e [!NOTE]\n\u003e This function has been marked `@deprecated` in favor of React Router's\n\u003e built-in type-safety, which provides strongly typed `params` to loaders,\n\u003e actions, and components. This helper has been kept primarily to assist in\n\u003e incremental migration.\n\n```typescript\nimport { useParams } from \"react-router\";\nimport { $params } from 'safe-routes'; // \u003c-- Import $params helper.\n\nexport const action = async ({ params }) =\u003e {\n  const { id } = $params(\"/posts/:id/update\", params) // \u003c-- It's type safe, try renaming `id` param.\n\n  // ...\n}\n\nexport default function Component() {\n  const params = useParams();\n  const { id } = $params(\"/posts/:id/update\", params);\n  ...\n}\n```\n\n### Typed route ids\n\nsafe-routes exports the `RouteId` type definition with the list of all valid route ids for your repository, and has a helper function `$routeId` that tells typescript to restrict the given string to one of the valid RouteId values.\n\n```typescript\nimport type { RouteId } from 'safe-routes';\nimport type { loader as postsLoader } from './_layout.tsx';\nimport { useRouteLoaderData } from 'react-router';\nimport { $routeId } from 'safe-routes';\n\nexport default function Post() {\n  const postList = useRouteLoaderData\u003ctypeof postsLoader\u003e($routeId('routes/posts/_layout'));\n}\n```\n\n### Basename support\n\nBasename is supported out of the box. If you have set a basename in your `vite.config.ts` and `react-router.config.ts`, safe-routes will automatically prepend the basename to the generated URLs.\n\n```typescript\n// react-router.config.ts\nimport type { Config } from \"@react-router/dev/config\";\n\nexport default {\n  basename: \"/blog\",\n} satisfies Config;\n```\n\n```typescript\nimport { $path } from 'safe-routes';\n\n$path('/posts/:id', { id: 6 }); // =\u003e /blog/posts/6\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyesmeck%2Fsafe-routes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyesmeck%2Fsafe-routes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyesmeck%2Fsafe-routes/lists"}