diff --git a/examples/with-url-object-routing/README.md b/examples/with-url-object-routing/README.md
new file mode 100644
index 0000000000000..e57f71c51b140
--- /dev/null
+++ b/examples/with-url-object-routing/README.md
@@ -0,0 +1,29 @@
+# URL object routing
+
+## How to use
+
+Download the example [or clone the repo](https://github.com/zeit/next.js):
+
+```bash
+curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-url-object-routing
+cd with-url-object-routing
+```
+
+Install it and run:
+
+```bash
+npm install
+npm run dev
+```
+
+Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
+
+```bash
+now
+```
+
+## The idea behind the example
+
+Next.js allows using [Node.js URL objects](https://nodejs.org/api/url.html#url_url_strings_and_url_objects) as `href` and `as` values for ` ` component and parameters of `Router#push` and `Router#replace`.
+
+This simplify the usage of parameterized URLs when you have many query values.
diff --git a/examples/with-url-object-routing/package.json b/examples/with-url-object-routing/package.json
new file mode 100644
index 0000000000000..e6fe7e8be43e2
--- /dev/null
+++ b/examples/with-url-object-routing/package.json
@@ -0,0 +1,13 @@
+{
+ "scripts": {
+ "dev": "node server.js",
+ "build": "next build",
+ "start": "NODE_ENV=production node server.js"
+ },
+ "dependencies": {
+ "next": "beta",
+ "path-match": "1.2.4",
+ "react": "^15.4.2",
+ "react-dom": "^15.4.2"
+ }
+}
diff --git a/examples/with-url-object-routing/pages/about.js b/examples/with-url-object-routing/pages/about.js
new file mode 100644
index 0000000000000..8d71f4887d3b1
--- /dev/null
+++ b/examples/with-url-object-routing/pages/about.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import Link from 'next/link'
+import Router from 'next/router'
+
+const href = {
+ pathname: '/about',
+ query: { name: 'zeit' }
+}
+
+const as = {
+ pathname: '/about/zeit',
+ hash: 'title-1'
+}
+
+const handleClick = () => Router.push(href, as)
+
+export default (props) => (
+
tag and doesn't have a href attribute we specify it so that repetition is not needed by the user
if (child.type === 'a' && !('href' in child.props)) {
- props.href = this.props.as || this.props.href
+ props.href = as || href
}
return React.cloneElement(child, props)
@@ -105,9 +122,10 @@ export default class Link extends Component {
}
function isLocal (href) {
- const origin = getLocationOrigin()
- return !/^(https?:)?\/\//.test(href) ||
- origin === href.substr(0, origin.length)
+ const url = parse(href, false, true)
+ const origin = parse(getLocationOrigin(), false, true)
+ return (!url.host || !url.hostname) ||
+ (origin.host === url.host || origin.hostname === url.hostname)
}
const warnLink = execOnce(warn)
diff --git a/lib/router/router.js b/lib/router/router.js
index ca9484fc56073..d939a7834866b 100644
--- a/lib/router/router.js
+++ b/lib/router/router.js
@@ -128,7 +128,12 @@ export default class Router extends EventEmitter {
return this.change('replaceState', url, as, options)
}
- async change (method, url, as, options) {
+ async change (method, _url, _as, options) {
+ // If url and as provided as an object representation,
+ // we'll format them into the string version here.
+ const url = typeof _url === 'object' ? format(_url) : _url
+ const as = typeof _as === 'object' ? format(_as) : _as
+
this.abortComponentLoad(as)
const { pathname, query } = parse(url, true)
diff --git a/package.json b/package.json
index 47f05613f3b11..243ce8b13d1db 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"babel-preset-es2015": "6.22.0",
"benchmark": "2.1.3",
"cheerio": "0.22.0",
- "chromedriver": "2.26.1",
+ "chromedriver": "2.28.0",
"coveralls": "2.11.16",
"cross-env": "3.1.4",
"fly": "2.0.5",
diff --git a/readme.md b/readme.md
index a026898d5d5c0..4127feaedadfe 100644
--- a/readme.md
+++ b/readme.md
@@ -271,6 +271,27 @@ Each top-level component receives a `url` property with the following API:
The second `as` parameter for `push` and `replace` is an optional _decoration_ of the URL. Useful if you configured custom routes on the server.
+##### With URL object
+
+
+ Examples
+
+
+
+The component ` ` can also receive an URL object and it will automatically format it to create the URL string.
+
+```jsx
+// pages/index.js
+import Link from 'next/link'
+export default () => (
+ Click here to read more
+)
+```
+
+That will generate the URL string `/about?name=Zeit`, you can use every property as defined in the [Node.js URL module documentation](https://nodejs.org/api/url.html#url_url_strings_and_url_objects).
+
#### Imperatively
@@ -303,6 +324,24 @@ The second `as` parameter for `push` and `replace` is an optional _decoration_ o
_Note: in order to programmatically change the route without triggering navigation and component-fetching, use `props.url.push` and `props.url.replace` within a component_
+##### With URL object
+You can use an URL object the same way you use it in a ` ` component to `push` and `replace` an url.
+
+```jsx
+import Router from 'next/router'
+
+const handler = () => Router.push({
+ pathname: 'about',
+ query: { name: 'Zeit' }
+})
+
+export default () => (
+ Click here to read more
+)
+```
+
+This uses of the same exact parameters as in the ` ` component.
+
##### Router Events
You can also listen to different events happening inside the Router.
diff --git a/test/integration/basic/pages/nav/index.js b/test/integration/basic/pages/nav/index.js
index d283ce58a5e83..23cf4d23a8ed9 100644
--- a/test/integration/basic/pages/nav/index.js
+++ b/test/integration/basic/pages/nav/index.js
@@ -1,5 +1,6 @@
import Link from 'next/link'
import { Component } from 'react'
+import Router from 'next/router'
let counter = 0
@@ -13,6 +14,12 @@ export default class extends Component {
this.forceUpdate()
}
+ visitQueryStringPage () {
+ const href = { pathname: '/nav/querystring', query: { id: 10 } }
+ const as = { pathname: '/nav/querystring/10', hash: '10' }
+ Router.push(href, as)
+ }
+
render () {
return (
@@ -20,6 +27,20 @@ export default class extends Component {
Empty Props
Self Reload
Shallow Routing
+
+
QueryString
+
+
this.visitQueryStringPage()}
+ style={linkStyle}
+ id='query-string-button'
+ >
+ Visit QueryString Page
+
+
This is the home.
Counter: {counter}
diff --git a/test/integration/basic/pages/nav/querystring.js b/test/integration/basic/pages/nav/querystring.js
index 740a4acfe0384..11b31e34b1478 100644
--- a/test/integration/basic/pages/nav/querystring.js
+++ b/test/integration/basic/pages/nav/querystring.js
@@ -8,7 +8,7 @@ export default class AsyncProps extends React.Component {
render () {
return (
-
+
Click here
diff --git a/test/integration/basic/test/client-navigation.js b/test/integration/basic/test/client-navigation.js
index b7bd740bbe5c5..3022502f31047 100644
--- a/test/integration/basic/test/client-navigation.js
+++ b/test/integration/basic/test/client-navigation.js
@@ -236,5 +236,33 @@ export default (context, render) => {
browser.close()
})
})
+
+ describe('with URL objects', () => {
+ it('should work with
', async () => {
+ const browser = await webdriver(context.appPort, '/nav')
+ const text = await browser
+ .elementByCss('#query-string-link').click()
+ .waitForElementByCss('.nav-querystring')
+ .elementByCss('p').text()
+ expect(text).toBe('10')
+
+ expect(await browser.url())
+ .toBe(`http://localhost:${context.appPort}/nav/querystring/10#10`)
+ browser.close()
+ })
+
+ it('should work with "Router.push"', async () => {
+ const browser = await webdriver(context.appPort, '/nav')
+ const text = await browser
+ .elementByCss('#query-string-button').click()
+ .waitForElementByCss('.nav-querystring')
+ .elementByCss('p').text()
+ expect(text).toBe('10')
+
+ expect(await browser.url())
+ .toBe(`http://localhost:${context.appPort}/nav/querystring/10#10`)
+ browser.close()
+ })
+ })
})
}