1.0.10 ā€¢ Published 4 days ago

@erboladaiorg/inventore-distinctio-sunt v1.0.10

Weekly downloads
-
License
MIT
Repository
github
Last release
4 days ago

Next.js Routes

@erboladaiorg/inventore-distinctio-sunt preview gif

What is this? šŸ§

@erboladaiorg/inventore-distinctio-sunt makes Next.js's next/link and next/router routes type safe with zero runtime overhead. @erboladaiorg/inventore-distinctio-sunt scans your pages directory and generates route types based on your application's routes.

@erboladaiorg/inventore-distinctio-sunt drops into your existing Next.js application with minimal configuration. You won't have to change any code, unless it finds some broken links!

Notice

If you are using Next.js's App Router you may not need this library. Next provides an experimental option to generate typed links. Note that at this time, Next's option only works for the app directory, and not pages. If you want type safety for pages, use this library.

Highlights

šŸ¦„ Zero config

šŸ’Ø Types only -- zero runtime

šŸ›  No more broken links

šŸŖ„ Route autocompletion

šŸ”— Supports all Next.js route types: static, dynamic, catch all and optional catch all

Installation & Usage šŸ“¦

  1. Add this package to your project:

    npm install @erboladaiorg/inventore-distinctio-sunt
    # or
    yarn add @erboladaiorg/inventore-distinctio-sunt
    # or
    pnpm add @erboladaiorg/inventore-distinctio-sunt
  2. Update your next.config.js:

    + const nextRoutes = require("@erboladaiorg/inventore-distinctio-sunt/config");
    + const withRoutes = nextRoutes();
    
    /** @type {import('next').NextConfig} */
    const nextConfig = {
      reactStrictMode: true,
    };
    
    - module.exports = nextConfig;
    + module.exports = withRoutes(nextConfig);
  3. Start or build your next project:

    npx next dev
    # or
    npx next build

That's it! A @types/@erboladaiorg/inventore-distinctio-sunt.d.ts file will be generated the first time you start your server. Check this file into version control. next/link and next/router type definitions have been augmented to verify your application's routes. No more broken links, and you get route autocompletion šŸ™Œ.

In development, whenever your routes change, your @types/@erboladaiorg/inventore-distinctio-sunt.d.ts file will automatically update.

If you would prefer to generate the route types file outside of next dev or next build you can also invoke the cli directly: npx @erboladaiorg/inventore-distinctio-sunt.

Examples šŸ› 

Link

Link's href prop is now typed based on your application routes:

import Link from "next/link";

<Link
  href={{
    pathname: "/foos/[foo]",
    query: { foo: "bar" },
  }}
>
  Bar
</Link>;

If the route doesn't require any parameters, you can also use a path string:

<Link href="/foo">Foo</Link>

useRouter

useRouter's returned router instance types for push, replace and query are now typed based on your application routes.

Identical to Link, push and replace now expect a UrlObject or path string:

push

import { useRouter } from "next/router";

const router = useRouter();
router.push({ pathname: "/foos/[foo]", query: { foo: "test" } });

replace

import { useRouter } from "next/router";

const router = useRouter();
router.replace({ pathname: "/" });

query

import { useRouter } from "next/router";

// query is typed as a union of all query parameters defined by your application's routes
const { query } = useRouter();

By default, query will be typed as the union of all possible query parameters defined by your application routes. If you'd like to narrow the type to fewer routes or a single page, you can supply a type argument:

import { useRouter } from "next/router";

const router = useRouter<"/foos/[foo]">();
// query is now typed as `{ foo?: string | undefined }`
router.query;

You can further narrow the query type by checking the router's isReady property.

import { useRouter } from "next/router";

const router = useRouter<"/foos/[foo]">();
// query is typed as `{ foo?: string | undefined }`
router.query;

if (router.isReady) {
  // query is typed as `{ foo: string }`
  router.query;
}

Checking isReady is necessary because of Next's Automatic Static Optimization. The router's query object will be empty for pages that are Automatic Static Optimized. After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object. See Next's documentation for more information. isReady will always return true for server rendered pages.

Route

If you want to use the generated Route type in your code, you can import it from @erboladaiorg/inventore-distinctio-sunt:

import type { Route } from "@erboladaiorg/inventore-distinctio-sunt";

Pathname

If you want a type for all possible pathnames you can achieve this via Route:

import type { Route } from "@erboladaiorg/inventore-distinctio-sunt";
type Pathname = Route["pathname"];

RoutedQuery

If you want to use the generated Query for a given Route, you can import it from @erboladaiorg/inventore-distinctio-sunt:

import type { RoutedQuery } from "@erboladaiorg/inventore-distinctio-sunt";

GetServerSidePropsContext

If you're using getServerSideProps consider using GetServerSidePropsContext from @erboladaiorg/inventore-distinctio-sunt. This is nearly identical to GetServerSidePropsContext from next, but further narrows types based on nextjs-route's route data.

import type { GetServerSidePropsContext } from "@erboladaiorg/inventore-distinctio-sunt";

export function getServerSideProps(
  context: GetServerSidePropsContext<"/foos/[foo]">,
) {
  // context.params will include `foo` as a string;
  const { foo } = context.params;
}

GetServerSideProps

If you're using getServerSideProps and TypeScript 4.9 or later, you can combine the satisfies operator with GetServerSideProps from @erboladaiorg/inventore-distinctio-sunt. This is nearly identical to GetServerSideProps from next, but further narrows types based on nextjs-route's route data.

import type { GetServerSideProps } from "@erboladaiorg/inventore-distinctio-sunt";

export const getServerSideProps = (async (context) => {
  // context.params will include `foo` as a string;
  const { foo } = context.params;
}) satisfies GetServerSideProps<{}, "/foos/[foo]">;

How does this work? šŸ¤”

@erboladaiorg/inventore-distinctio-sunt generates types for the pathname and query for every page in your pages directory. The generated types are written to @types/@erboladaiorg/inventore-distinctio-sunt.d.ts which is automatically referenced by your Next project's tsconfig.json. @types/@erboladaiorg/inventore-distinctio-sunt.d.ts redefines the types for next/link and next/router and applies the generated route types.

What if I need a runtime?

There are some cases where you may want to generate a type safe path from a Route object, such as when fetching from an API route or serving redirects from getServerSideProps. These accept strings instead of the Route object that Link and useRouter accept. Because these do not perform the same string interpolation for dynamic routes, runtime code is required instead of a type only solution.

For these cases, you can use route from @erboladaiorg/inventore-distinctio-sunt:

fetch

import { route } from "@erboladaiorg/inventore-distinctio-sunt";

fetch(route({ pathname: "/api/foos/[foo]", query: { foo: "foobar" } }));

getServerSideProps

import { route, type GetServerSidePropsContext } from "@erboladaiorg/inventore-distinctio-sunt";

export function getServerSideProps(context: GetServerSidePropsContext) {
  return {
    redirect: {
      destination: route({ pathname: "/foos/[foo]", query: { foo: "foobar" } }),
      permanent: false,
    },
  };
}

Internationalization (i18n)

@erboladaiorg/inventore-distinctio-sunt refines Link and useRouter based on your Nextjs i18n configuration.

The following next.config.js:

module.exports = withRoutes({
  i18n: {
    defaultLocale: "de-DE",
    locales: ["de-DE", "en-FR", "en-US"],
  },
});

Will type Link and useRouter's locale as 'de-DE' | 'en-FR' | 'en-US'. All other i18n properties (defaultLocale, domainLocales and locales) are also typed.

If you want to use the generated Locale type, you can import it from @erboladaiorg/inventore-distinctio-sunt:

import { Locale } from "@erboladaiorg/inventore-distinctio-sunt";

Configuration

You can pass the following options to nextRoutes in your next.config.js:

const nextRoutes = require("@erboladaiorg/inventore-distinctio-sunt/config");
const withRoutes = nextRoutes({
  outDir: "types",
  cwd: __dirname,
});
  • outDir: The file path indicating the output directory where the generated route types should be written to (e.g.: "types"). The default is to create the file in the same folder as your next.config.js file.

  • cwd: The path to the directory that contains your next.config.js file. This is only necessary for non standard project structures, such as nx. If you are an nx user getting the Could not find a Next.js pages directory error, use cwd: __dirname.

Troubleshooting

Could not find a Next.js pages directory

Non standard project structures, such as those using nx, require that users supply a path to their next.config.js. For nx, this is because nx introduces wrapping layers that invoke commands differently than using the next cli directly.

Solution:

const nextRoutes = require("@erboladaiorg/inventore-distinctio-sunt/config");
const withRoutes = nextRoutes({
+  cwd: __dirname
});

Contributing šŸ‘«

PR's and issues welcomed! For more guidance check out CONTRIBUTING.md

Are you interested in bringing a @erboladaiorg/inventore-distinctio-sunt like experience to another framework? Open an issue and let's collaborate.

Licensing šŸ“ƒ

See the project's MIT License.

languagerdsquerypluginapolloglobtextmobileurlsextrareactfast-copyeditorposegetterobjectkeyformattinglibphonenumberES63dendpointrobusttaskxhrmoveframeworkexecutablefast-deep-clonebindlogstatelesssetImmediatemakeObject.keysRFC-6455weakmaptsReflect.getPrototypeOfstringUint8ArrayextendregexppromisestdlibdatastructuretypeerrorsestoobjectlimitedjsxarrayswalkautoprefixertapeECMAScript 3cjkfromES5stableSymbol.toStringTaghookformUint32ArrayequalityhasOwnPropertyguiddebugflatMapairbnbserializersharedarraybufferAsyncIteratortrimEndbatchcmdArray.prototype.findLastlasteventsprogressargvjsonextensioncolorsglacierbeanstalk__proto__dataStreamsmimetypesECMAScript 7routeunicodeshebangquerystringdependency managerfoldercoerciblerequiretypestapeslintpluginobjinferenceelasticachebyteLengthFloat64Arraytddmulti-packagefunctionUint8ClampedArrayjsaccessorObjectdataviewhasdefineutilmatchkarmarmdirimportexportgroupByes6limitdeterministicfullwidthflattenenvironmentsarraybufferWebSocketsnpmicubrowserrm -frdeepelectronvariablesString.prototype.matchAllloadbalancingprivate datatoolkit$.extendinequalArray.prototype.flatquoteECMAScript 2021StyleSheetnamecloudwatchObject.assignhasOwnmkdirslazycompilerfunctionallruwordbreakthroatPromiseimmerescapesnsenvironmentjQueryfull-widthpreserve-symlinksmimeperformancepromisesisConcatSpreadableArrayBuffer.prototype.sliceworkspace:*es5createconcatpostcss-plugines8stringifyserializeoptimizervestisRxJSECMAScript 2015fsPushxtermmodulepathtypeperformantastenderhttpseslintconfigTypeBoxcopypackageargsstylingES2021RegExp.prototype.flagsmodulesiconvnegative zerotrimRightassigntrimStartboundenvkinesispropertiesWebSocketnameseslint-pluginECMAScript 2020deleteentriesajaxjapaneseintrinsicimportcss-in-jsjavascriptio-tsratelimitshimeslinttakeredirectutilityponyfillmapreducegroupwgetspeedkoreanassertionexpressES2023callbacka11ydefaultpopmotionreact posereact-hookswordwrapcommandtermWeakMapcodeslaunch.envsymbolsuperagentemrmrushrinkwraptypedarrayES8postcssuninstallcommanderbundlingresolveebsavaassertECMAScript 6searchtypanionschemestylesterminalsyntaxrangeerrormapes2015rapidECMAScript 2023sortparentssortedECMAScript 2018inspectpatchdeep-copyasciiECMAScript 2016Float32ArrayclonedebuggerECMAScript 2019dataViewreact animationeast-asian-widthwalkingbddBigInt64Arrayprotocol-buffersrequestbrowserslistreadcall-bindtostringtagdefinePropertyhashlookphonehelpersafelocationcloudformationnopeduplexshamefficienthttpwebsiteReactiveExtensionssymlinkstreamsgetintrinsicclass-validatorqueueObject.fromEntriesanimationparseES2016mochafilterqueueMicrotaskflatspringECMAScript 5ES2019testBigUint64ArrayInt8Arraywhichpackage managergdprremovepackage.jsonES3toSortedfileURLconcurrencycryptodropttycharsetfindLastIndexInt32ArrayconvertpersistentbrowserlistindicatorpipeiamamazonObject.getPrototypeOftrimLeftpicomatchfullfunctionsparsingbreaknegativeemitieclientstringifierargparsestoragegatewayfixed-widthautoscalingroute53xdgURLSearchParamsinstallfastregularjsdiffpackagesmomentec2colorregular expressionESes2017ansisidevarsconfigrm -rfemojigetOwnPropertyDescriptorsetweblockfilefastcopylistenersRegExp#flagscharactersmime-dbupArrayBuffer#slicesomecloudfrontestreeenumerablejestgetMicrosoftIteratorbannerredactflagcsscalles7make dircheckJSONutil.inspecthookscommand-lineglobals256ES2015collection.es6es2018idObservablesObject.definePropertyObject.isreducerxdg-openTypedArraycollectiondayjsES2018curlprocesslogginghas-owncall-boundfunction.lengthslotreduxpnpm9dirhardlinksbusybyteOffsetArray.prototype.flattenfind-upweaksettimeurlloggergetoptvalidationTypeScriptlinewrapredux-toolkitownES2017propopenES7ECMAScript 2017valuesvalidatezodObservablepositivesimpledbinstallercolumnsincludesUnderscoreassertsaccessibilityreadablefpserrormonorepoexecgenericswriteeventEmitterES2020vpcprototypebytefindWeakSetsharediterationMapserializationESnextstartObject.entriesoutputpolyfillinternalarguments
1.0.10

4 days ago

1.0.9

5 days ago

1.0.8

6 days ago

1.0.7

7 days ago

1.0.6

8 days ago

1.0.5

9 days ago

1.0.4

10 days ago

1.0.3

11 days ago

1.0.2

12 days ago

1.0.1

13 days ago

1.0.0

13 days ago