Thanks to @zeit/next-typescript, we can add TypeScript support to NextJS very simply.
Since next@7 relies on babel@7, this plugin relies on @babel/preset-typescript which means that:
- you leave the transpiling to babel (which will strip off the TypeScript types)
- you only do the type checking with TypeScript either:
- in your IDE
- in the command line with
tsc
- with fork-ts-checker-webpack-plugin
The plugin ships a preset to add to your .babelrc
(@zeit/next-typescript/babel
), containing the different preset that will match TypeScript config.
Basic install:
yarn add --dev @zeit/next-typescript @types/react @types/next
To add basic type checking: yarn add --dev typescript
(and add a type-check
task executing: tsc --noEmit
).
Resources:
- @zeit/next-typescript
- Microsoft/TypeScript-Babel-Starter
- deptno/next.js-typescript-starter-kit
- Limitations of using @babel/preset-typescript
Since transpiling is done with babel in development, in test, we do the same with babel-jest.
- The "test" section of the
.babelrc
file will be used - The
jest.config.js
file is taken in account by default by jest - I configured the
src/setupTests.js
file like in CRA to be loaded before tests start
With shipped version of babel-core
(different packages using v6 - not @babel/core
), I had the following error:
Test suite failed to run
Plugin 0 specified in "~/nextjs-movie-browser/node_modules/next/babel.js" provided an invalid property of "default" (While processing preset: "~/nextjs-movie-browser/node_modules/next/babel.js")
I fixed it with:
yarn add babel-core@^7.0.0-bridge --dev
In order to fix the peerDependencie to babel-core
I used babel-core@^7.0.0-bridge
, a version of babel-core
that requires @babel/core
under the hood - see full explanation.
Resources:
By default, you start your app with next start
. If you need to customize routes, use route patterns, you will have to make your own server entry point.
Resources:
Resources:
I'm relying on next-i18next, a next.js plugin based on i18next and react-i18next.
It handles SSR out of the box as well as namespace codesplitting (only sends down to the client the translations that it needs).
The tmdb API offers a lot of languages. I wanted to be able to consume all of them without having to translate my UI for each of them. I managed to properly fallback the UI to a default language (english) when translations were missing but still render the API content in the language the user asked for (all that with SSR working).
The translation files of your UI are in static/locales/${languageCode}/
.
For the UI, I wanted to be able to handle language codes like "en", "fr" ... (ISO 639-1 codes).
For the API calls, since it handles it, I wanted to handle more specific codes: a combination of ISO 639-1 codes (languages) and ISO 3166-1 (countries), to be able to be more granular - some languages are available in different regions around the world, we might want to expose specific versions. Example:
- "fr-FR" is french
- "pt-PT" is portuguese
- "pt-BR" is portuguese (bresilian variation)
Reusable code is in src/services/i18n.
When you use next-i18next
, you wrap your root page (_app.tsx
) with the HOC appWithTranslation
which will pass via context the i18n instance.
Then you use the withNamespaces
HOC to expose t
(the translate function) to the component you want to translate.
And I added a specific LanguageManager
that lets you manage language codes / change language on the fly.
You can test a translated component by using the renderI18nNamespacesWrappedComponent
utility which mocks appWithTranslation
.
You can test a translated component that also uses the specific feature I added like language codes and switch language with renderI18nWithLanguageManagerProvider
.
Anyway, you'll have to mock withNamespaces
from next-i18next
with my own withNamespacesMock
:
// src/setupTests.js
const { withNamespacesMock } = require("./services/i18n/NamespaceMock");
jest.mock("react-i18next", () => ({
withNamespaces: withNamespacesMock
}));
Note: If you use shallow rendering (like Enzyme), you don't need everything I explained above. Since you will be rendering only ONE component deep, you can settle with exporting undecorated version of your components (without withNamespaces
) and inject a stub of t
prop - read more.
Since I'm using react-testing-library, I'm rendering a whole tree in jsdom, which is why I had to mock the context to pass the i18n
instance accross the whole tree.
When you test a code base that contains calls to an external api server, you have to make a choice in your test strategy.
- You're making real api calls, so you're also testing the server
- 👍 Guaranteed to work in production, once tests pass
- 👎 Much slower, false negative (network failures ...)
- You don't need to create/record any fixtures nor setup a mocking system
- You're not making api calls, so
- 👍 Very fast, determinist (response won't change unless you change the mock)
- 👎 Result may differ in production if mocks aren't up to date
I wrote a library axios-mock-manager to solve this problem.
In ordre to trigger the build of your Next app on each deploy, add the following:
{
"scripts": {
"heroku-postbuild": "next build"
}
}
Sources:
heroku config:set NPM_CONFIG_PRODUCTION=false
: Make sure to also download dev dependencies (like babel, typescript ...) or hoist them in your regular dependencies.heroku config:set NEXTJS_APP_CLIENT_TMDB_API_KEY=your-api-key
: Set your api key
Tslint is a linter for both .ts
and .js
files, if you know eslint, it works the same way (with configurable rules and sharable configurations).
On this project, I'm using the following configs:
- tslint:recommended
- tslint-config-airbnb - a TypeScript version of the eslint-config-airbnb
- tslint-config-prettier - a config that disables all conflicting rules between Tslint and prettier (Tslint is for linting, prettier is for formatting)
To your tslint.json file, add:
{
"jsRules": true
}
Make sure you lint the js files in your linting script in your package.json:
{
"scripts": {
"lint": "npx tslint --project tsconfig.json -c tslint.json '**/*.{js,ts,tsx}'"
}
}
Install the Tslint plugin.
If you want feedback for js files as well as for ts ones, to your .vscode/settings.json, add:
{
"tslint.jsEnable": true
}