Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): Add support for relative links #24054

Merged
merged 18 commits into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/docs/gatsby-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ const Link = ({ children, to, activeClassName, partiallyActive, ...other }) => {
export default Link
```

### Relative links

The `<Link />` component follows [the behavior of @reach/router](https://reach.tech/router/nesting) by ignoring trailing slashes and treating each page as if it were a directory when resolving relative links. For example if you are on either `/blog/my-great-page` or `/blog/my-great-page/` (note the trailing slash), a link to `../second-page` will take you to `/blog/second-page`.

### File Downloads

You can similarly check for file downloads:
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/linking-between-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ The above code will add a link to the contact page, automatically rendered in HT

> **Note:** the value `"/"` for the `to` property will take users to the home page.

## Using relative links in the `<Link />` component

Relative links are ones where the `to` property doesn't start with a `/`. These behave slightly differently from relative links in `<a>` tags, and instead follow [the behavior of @reach/router](https://reach.tech/router/nesting). This avoids confusion with trailing slashes by ignoring them entirely and treating every page as if it were a directory. For example, if you are on either `/blog/my-great-page` or `/blog/my-great-page/` (note the trailing slash), a link to `../second-page` will take you to `/blog/second-page`. Similarly, if you are on `/blog` or `/blog/` a link to `hello-world` will take you to `/blog/hello-world`.

## Using `<a>` for external links

If you are linking to pages not handled by your Gatsby site (such as on a different domain), you should use the native HTML `<a>` tag instead of Gatsby Link.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,42 @@ describe(`navigation`, () => {
cy.location(`pathname`).should(`equal`, `/`)
})

describe(`relative links`, () => {
it(`can navigate to a subdirectory`, () => {
cy.getTestElement(`subdir-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory/page-1`)
})

it(`can navigate to a sibling page`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-2-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory/page-2`)
})

it(`can navigate to a parent page`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-parent-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory`)
})

it(`can navigate to a sibling page programatically`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-2-button-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory/page-2`)
})
})

describe(`non-existent route`, () => {
beforeEach(() => {
cy.getTestElement(`broken-link`).click().waitForRouteChange()
Expand Down
3 changes: 3 additions & 0 deletions e2e-tests/development-runtime/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const IndexPage = ({ data }) => (
<Link to="/__non_existent_page__/" data-testid="broken-link">
Go to a broken link
</Link>
<Link to="subdirectory/page-1" data-testid="subdir-link">
Go to subdirectory
</Link>
<h2>Blog posts</h2>
<ul>
{data.posts.edges.map(({ node }) => (
Expand Down
23 changes: 23 additions & 0 deletions e2e-tests/development-runtime/src/pages/subdirectory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from "react"
import { Link, navigate } from "gatsby"

import Layout from "../../components/layout"

const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<Link data-testid="page-2-link" to="./page-2/">
Go to page 2
</Link>
<button
data-testid="page-2-button-link"
onClick={() => navigate(`./page-2/`)}
>
Go to page 2 with navigate()
</button>
</Layout>
)

export default IndexPage
26 changes: 26 additions & 0 deletions e2e-tests/development-runtime/src/pages/subdirectory/page-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react"
import { Link, navigate } from "gatsby"

import Layout from "../../components/layout"

const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<Link data-testid="page-2-link" to="../page-2/">
Go to page 2
</Link>
<Link data-testid="page-parent-link" to="..">
Go up
</Link>
<button
data-testid="page-2-button-link"
onClick={() => navigate(`../page-2/`)}
>
Go to page 2 with navigate()
</button>
</Layout>
)

export default IndexPage
16 changes: 16 additions & 0 deletions e2e-tests/development-runtime/src/pages/subdirectory/page-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from "react"
import { Link } from "gatsby"

import Layout from "../../components/layout"

const SecondPage = () => (
<Layout>
<h1>Hi from the second page</h1>
<p>Welcome to page 2</p>
<Link data-testid="index-link" to="../page-1">
Go back to page 1
</Link>
</Layout>
)

export default SecondPage
36 changes: 36 additions & 0 deletions e2e-tests/path-prefix/cypress/integration/navigate.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ describe(`navigate`, () => {
.should(`eq`, withTrailingSlash(pathPrefix))
})

describe(`relative links`, () => {
it(`can navigate to a subdirectory`, () => {
cy.getTestElement(`subdir-link`)
.click()
.location(`pathname`)
.should(`eq`, `${pathPrefix}/subdirectory/page-1`)
})

it(`can navigate to a sibling page`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-2-link`)
.click()
.location(`pathname`)
.should(`eq`, `${pathPrefix}/subdirectory/page-2`)
})

it(`can navigate to a parent page`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-parent-link`)
.click()
.location(`pathname`)
.should(`eq`, `${pathPrefix}/subdirectory`)
})

it(`can navigate to a sibling page programatically`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-2-button-link`)
.click()
.location(`pathname`)
.should(`eq`, `${pathPrefix}/subdirectory/page-2`)
})
})

it(`can navigate to 404`, () => {
cy.getTestElement(`404-link`).click().waitForRouteChange()

Expand Down
3 changes: 3 additions & 0 deletions e2e-tests/path-prefix/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const IndexPage = () => (
<Link data-testid="404-link" to="/not-existing-page">
Go to not existing page
</Link>
<Link data-testid="subdir-link" to="subdirectory/page-1">
Go to subdirectory
</Link>
</Layout>
)

Expand Down
23 changes: 23 additions & 0 deletions e2e-tests/path-prefix/src/pages/subdirectory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from "react"
import { Link, navigate } from "gatsby"

import Layout from "../../components/layout"

const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<Link data-testid="page-2-link" to="./page-2/">
Go to page 2
</Link>
<button
data-testid="page-2-button-link"
onClick={() => navigate(`./page-2/`)}
>
Go to page 2 with navigate()
</button>
</Layout>
)

export default IndexPage
26 changes: 26 additions & 0 deletions e2e-tests/path-prefix/src/pages/subdirectory/page-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react"
import { Link, navigate } from "gatsby"

import Layout from "../../components/layout"

const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<Link data-testid="page-2-link" to="../page-2/">
Go to page 2
</Link>
<Link data-testid="page-parent-link" to="..">
Go up
</Link>
<button
data-testid="page-2-button-link"
onClick={() => navigate(`../page-2/`)}
>
Go to page 2 with navigate()
</button>
</Layout>
)

export default IndexPage
16 changes: 16 additions & 0 deletions e2e-tests/path-prefix/src/pages/subdirectory/page-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from "react"
import { Link } from "gatsby"

import Layout from "../../components/layout"

const SecondPage = () => (
<Layout>
<h1>Hi from the second page</h1>
<p>Welcome to page 2</p>
<Link data-testid="index-link" to="../page-1">
Go back to page 1
</Link>
</Layout>
)

export default SecondPage
38 changes: 38 additions & 0 deletions e2e-tests/production-runtime/cypress/integration/1-production.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,44 @@ describe(`Production build tests`, () => {
.should(`equal`, `/page-2/`)
})

describe(`relative links`, () => {
it(`should navigate to a subdirectory`, () => {
cy.visit(`/`)
.waitForRouteChange()
.getTestElement(`subdir-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory/page-1`)
})

it(`can navigate to a sibling page`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-2-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory/page-2`)
})

it(`can navigate to a parent page`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-parent-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory`)
})

it(`can navigate to a sibling page programatically`, () => {
cy.visit(`/subdirectory/page-1`)
.waitForRouteChange()
.getTestElement(`page-2-button-link`)
.click()
.location(`pathname`)
.should(`eq`, `/subdirectory/page-2`)
})
})

it(`should show 404 page when clicking a link to a non-existent page route`, () => {
cy.visit(`/`).waitForRouteChange()

Expand Down
5 changes: 5 additions & 0 deletions e2e-tests/production-runtime/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ const IndexPage = ({ pageContext }) => (
Go to page with unicode path
</Link>
</li>
<li>
<Link to="subdirectory/page-1" data-testid="subdir-link">
Go to subdirectory
</Link>
</li>
</ul>
</Layout>
)
Expand Down
23 changes: 23 additions & 0 deletions e2e-tests/production-runtime/src/pages/subdirectory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from "react"
import { Link, navigate } from "gatsby"

import Layout from "../../components/layout"

const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<Link data-testid="page-2-link" to="./page-2/">
Go to page 2
</Link>
<button
data-testid="page-2-button-link"
onClick={() => navigate(`./page-2/`)}
>
Go to page 2 with navigate()
</button>
</Layout>
)

export default IndexPage
26 changes: 26 additions & 0 deletions e2e-tests/production-runtime/src/pages/subdirectory/page-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react"
import { Link, navigate } from "gatsby"

import Layout from "../../components/layout"

const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<Link data-testid="page-2-link" to="../page-2/">
Go to page 2
</Link>
<Link data-testid="page-parent-link" to="..">
Go up
</Link>
<button
data-testid="page-2-button-link"
onClick={() => navigate(`../page-2/`)}
>
Go to page 2 with navigate()
</button>
</Layout>
)

export default IndexPage
16 changes: 16 additions & 0 deletions e2e-tests/production-runtime/src/pages/subdirectory/page-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from "react"
import { Link } from "gatsby"

import Layout from "../../components/layout"

const SecondPage = () => (
<Layout>
<h1>Hi from the second page</h1>
<p>Welcome to page 2</p>
<Link data-testid="index-link" to="../page-1">
Go back to page 1
</Link>
</Layout>
)

export default SecondPage
Loading