Skip to content

Commit

Permalink
refactor: Use rewrites instead of custom server (#689)
Browse files Browse the repository at this point in the history
  • Loading branch information
isaachinman committed May 2, 2020
1 parent 9eb6add commit fd7125a
Show file tree
Hide file tree
Showing 24 changed files with 2,046 additions and 2,048 deletions.
13 changes: 8 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ commands:
build-and-test:
steps:
- checkout
- run:
name: Install rsync
command: sudo apt install rsync
- restore_cache:
name: Restore cache (main)
keys:
Expand Down Expand Up @@ -38,19 +41,19 @@ commands:
command: yarn test

jobs:
node-v8:
node-v10:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:10-browsers
steps:
- build-and-test
node-v10:
node-v12:
docker:
- image: circleci/node:10-browsers
- image: circleci/node:12-browsers
steps:
- build-and-test

workflows:
node-multi-build:
jobs:
- node-v8
- node-v10
- node-v12
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ out

# Testing
coverage/
.e2e

# npm
package-lock.json
23 changes: 6 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,8 @@ It's recommended to export this `NextI18Next` instance from a single file in you
After creating and exporting your `NextI18Next` instance, you need to take the following steps to get things working:

1. Create an `_app.js` file inside your `pages` directory, and wrap it with the `NextI18Next.appWithTranslation` higher order component (HOC). You can see this approach in the [examples/simple/pages/_app.js](./examples/simple/pages/_app.js).
Your app component must either extend `App` if it's a class component or define a `getInitialProps` if it's a function component [(explanation here)](https://github.com/isaachinman/next-i18next/issues/615#issuecomment-575578375).
2. Create a `server.js` file inside your root directory, initialise an [express](https://www.npmjs.com/package/express) server, and use the `nextI18NextMiddleware` middleware with your `nextI18Next` instance passed in. You can see this approach in the [examples/simple/server.js](./examples/simple/server.js).
3. Update the scripts in `package.json` to:
```
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}
```
For more info, see [the NextJs section on custom servers](https://github.com/zeit/next.js#custom-server-and-routing).
Your app component must either extend `App` if it's a class component or define a `getInitialProps` if it's a functional component [(explanation here)](https://github.com/isaachinman/next-i18next/issues/615#issuecomment-575578375).
2. Create a `next.config.js` file inside your root directory if you want to use locale subpaths. You can see this approach in the [examples/simple/next.config.js](./examples/simple/next.config.js).

Note: You can pass `shallowRender: true` into config options to avoid triggering getInitialProps when `changeLanguage` method is invoked.

Expand Down Expand Up @@ -138,6 +127,8 @@ new NextI18Next({
})
```

The `localeSubpaths` object must also be passed into `next.config.js`, via the `nextI18NextRewrites` util, which you can import from `next-i18next/rewrites`.

The `localeSubpaths` option is a key/value mapping, where keys are the locale itself (case sensitive) and values are the subpath without slashes.

Now, all your page routes will be duplicated across all your locale subpaths. Here's an example:
Expand All @@ -158,7 +149,7 @@ myapp.com/german
myapp.com/eng
```

When using the localeSubpaths option, our middleware may redirect without calling any subsequent middleware. Therefore, if there are any critical middleware that must run before this redirect, ensure that you place it before the `nextI18NextMiddleware` middleware.
When using the localeSubpaths option, our middleware will redirect as needed in the wrapped `getInitialProps` one level above your `_app`, so none of your code will be called.

The main "gotcha" with locale subpaths is routing. We want to be able to route to "naked" routes, and not have to worry about the locale subpath part of the route:

Expand All @@ -183,8 +174,7 @@ const SomeLink = () => (
)
```

We can also navigate imperatively with locale subpaths by importing `Router` from your `NextI18Next` instance.
The exported Router shares the same API as the native Next Router. The push, replace, and prefetch functions will automatically prepend locale subpaths.
We can also navigate imperatively with locale subpaths by importing `Router` from your `NextI18Next` instance. The exported Router shares the same API as the native Next Router. The push, replace, and prefetch functions will automatically prepend locale subpaths.

```jsx
import React from 'react'
Expand Down Expand Up @@ -259,7 +249,6 @@ _This table contains options which are specific to next-i18next. All other [i18n
## Notes

- [`next export` will result in a _client-side only_ React application.](https://github.com/isaachinman/next-i18next/issues/10)
- [Serverless (e.g. Now 2.0) is not currently supported](https://github.com/isaachinman/next-i18next/issues/274).
- [To add a `lang` attribute to your top-level html DOM node, you must create a `_document.js` file.](https://github.com/isaachinman/next-i18next/issues/20#issuecomment-443461652)
- [Localising `next/head` requires special consideration due to NextJs internals](https://github.com/isaachinman/next-i18next/issues/251#issuecomment-479421852).

Expand Down
25 changes: 11 additions & 14 deletions __tests__/middlewares/next-i18next-middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@ describe('next-18next middleware', () => {
i18n: testI18NextConfig
}
res = {
redirect: jest.fn(),
header: jest.fn()
end: jest.fn(),
writeHead: jest.fn(),
setHeader: jest.fn()
}
next = jest.fn()
})

afterEach(() => {
i18nextMiddleware.handle.mockClear()

res.redirect.mockReset()
res.header.mockReset()
res.writeHead.mockReset()
res.setHeader.mockReset()
})

const callAllMiddleware = () => {
Expand Down Expand Up @@ -76,7 +77,7 @@ describe('next-18next middleware', () => {
expect(req.i18n).toBeDefined()
})

it('adds lng to query parameters and removes from url for i18next processing', () => {
it('adds lng to query parameters', () => {
const language = 'de'
const subpath = 'german'
req = {
Expand All @@ -95,11 +96,7 @@ describe('next-18next middleware', () => {

callAllMiddleware()

expect(req.url).toBe('/page1')
expect(req.query).toEqual({
lng: language,
subpath,
})
expect(req.url).toBe('/german/page1')

expect(next).toBeCalledTimes(1)
})
Expand Down Expand Up @@ -128,10 +125,10 @@ describe('next-18next middleware', () => {
expect(req.url).toBe('/page1')
expect(req.query).toEqual({})

expect(res.redirect).toHaveBeenCalledWith(302, '/german/page1')
expect(res.header).toHaveBeenNthCalledWith(1, 'Cache-Control', 'private, no-cache, no-store, must-revalidate')
expect(res.header).toHaveBeenNthCalledWith(2, 'Expires', '-1')
expect(res.header).toHaveBeenNthCalledWith(3, 'Pragma', 'no-cache')
expect(res.writeHead).toHaveBeenCalledWith(302, { Location: '/german/page1' })
expect(res.setHeader).toHaveBeenNthCalledWith(1, 'Cache-Control', 'private, no-cache, no-store, must-revalidate')
expect(res.setHeader).toHaveBeenNthCalledWith(2, 'Expires', '-1')
expect(res.setHeader).toHaveBeenNthCalledWith(3, 'Pragma', 'no-cache')
expect(next).toBeCalledTimes(0)
})

Expand Down
19 changes: 19 additions & 0 deletions examples/simple/__tests__/e2e/locale-subpaths-all.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const { nextI18NextRewrites } = require('next-i18next/rewrites')

const localeSubpaths = {
en: 'en',
de: 'de',
}

module.exports = {
publicRuntimeConfig: {
localeSubpaths,
},
experimental: {
async rewrites() {
return [
...nextI18NextRewrites(localeSubpaths)
]
}
}
}
18 changes: 18 additions & 0 deletions examples/simple/__tests__/e2e/locale-subpaths-foreign.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { nextI18NextRewrites } = require('next-i18next/rewrites')

const localeSubpaths = {
de: 'de',
}

module.exports = {
publicRuntimeConfig: {
localeSubpaths,
},
experimental: {
async rewrites() {
return [
...nextI18NextRewrites(localeSubpaths)
]
}
}
}
18 changes: 1 addition & 17 deletions examples/simple/i18n.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
/*
Do not copy/paste this file. It is used internally
to manage end-to-end test suites.
*/

const NextI18Next = require('next-i18next').default
const { localeSubpaths } = require('next/config').default().publicRuntimeConfig

const localeSubpathVariations = {
none: {},
foreign: {
de: 'de',
},
all: {
en: 'en',
de: 'de',
},
}

module.exports = new NextI18Next({
otherLanguages: ['de'],
localeSubpaths: localeSubpathVariations[localeSubpaths],
localeSubpaths,
})
4 changes: 0 additions & 4 deletions examples/simple/index.js

This file was deleted.

15 changes: 12 additions & 3 deletions examples/simple/next.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
const { nextI18NextRewrites } = require('next-i18next/rewrites')

const localeSubpaths = {}

module.exports = {
publicRuntimeConfig: {
localeSubpaths: typeof process.env.LOCALE_SUBPATHS === 'string'
? process.env.LOCALE_SUBPATHS
: 'none',
localeSubpaths,
},
experimental: {
async rewrites() {
return [
...nextI18NextRewrites(localeSubpaths)
]
}
}
}
8 changes: 4 additions & 4 deletions examples/simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
"node": ">=8"
},
"scripts": {
"dev": "node index.js",
"dev": "next",
"build": "next build",
"start": "NODE_ENV=production node index.js"
"start": "next start -p ${PORT:=3000}"
},
"devDependencies": {},
"dependencies": {
"express": "^4.16.4",
"i18next": "^14.0.1",
"next": "^9.1.6",
"next": "^9.3.5",
"next-i18next": "link:../../"
}
}
}
22 changes: 0 additions & 22 deletions examples/simple/server.js

This file was deleted.

Loading

0 comments on commit fd7125a

Please sign in to comment.