When using gatsby-remark-autolink-headers, auto generated anchors link gets / in front of all my anchors. In my setup, this results it to go to base url when I need it to be relative.
Here is the html that I get for the anchor:
<a href="/#basic-card" aria-label="basic card permalink" class="anchor after">
<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
<path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path>
</svg>
</a>
System:
OS: macOS 10.15.2
CPU: (12) x64 Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 10.15.3 - ~/.nvm/versions/node/v10.15.3/bin/node
Yarn: 1.19.1 - /usr/local/bin/yarn
npm: 6.13.4 - ~/.nvm/versions/node/v10.15.3/bin/npm
Languages:
Python: 2.7.16 - /usr/bin/python
Browsers:
Chrome: 80.0.3987.122
Safari: 13.0.4
gatsby-config.js:
module.exports = {
siteMetadata: {
title: `Anvil Design System`,
description: ``,
author: `@sevicetitan`,
menuLinks:[
{
name:'Foundations',
link:'/foundation/'
},
{
name:'Patterns',
link:'/pattern/'
},
{
name:'Components',
link:'/component/',
},
{
name:'Resources',
link:'/resource/'
},
]
},
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-plugin-sass`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `foundation`,
path: `${__dirname}/content/foundation`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `component`,
path: `${__dirname}/content/component`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pattern`,
path: `${__dirname}/content/pattern`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `resource`,
path: `${__dirname}/content/resource`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.mdx`, `.md`],
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-autolink-headers`,
options: {
offsetY: `100`,
isIconAfterHeader: true,
removeAccents: true,
},
},
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 1035,
sizeByPixelDensity: true,
},
},
]
},
},
{
resolve: 'gatsby-redirect-from',
options: {
query: 'allMdx'
}
},
`gatsby-plugin-meta-redirect`,
// {
// resolve: 'gatsby-plugin-algolia',
// options: require(`./gatsby-pluglin-algolia-config.js`)
// }
],
}
package.json:
{
"name": "docs",
"private": true,
"description": "A simple starter to get up and developing quickly with Gatsby",
"version": "0.1.0",
"author": "ServiceTitan",
"devDependencies": {
"@mdx-js/mdx": "^1.5.3",
"@mdx-js/react": "^1.5.3",
"@servicetitan/anvil-fonts": "^3.0.0",
"@servicetitan/anvil-icons": "^3.5.0",
"@servicetitan/design-system": "^3.4.0",
"@servicetitan/tokens": "^3.1.0",
"algoliasearch": "^3.35.1",
"classnames": "2.2.6",
"dotenv": "^8.2.0",
"gatsby": "2.18.12",
"gatsby-image": "^2.2.34",
"gatsby-plugin-algolia": "^0.5.0",
"gatsby-plugin-manifest": "^2.2.31",
"gatsby-plugin-mdx": "^1.0.64",
"gatsby-plugin-meta-redirect": "^1.1.1",
"gatsby-plugin-offline": "^3.0.27",
"gatsby-plugin-page-creator": "^2.1.37",
"gatsby-plugin-react-helmet": "^3.1.16",
"gatsby-plugin-sass": "^2.1.26",
"gatsby-plugin-sharp": "^2.3.5",
"gatsby-plugin-styled-components": "^3.1.16",
"gatsby-redirect-from": "^0.2.1",
"gatsby-remark-autolink-headers": "^2.1.21",
"gatsby-remark-images": "^3.1.39",
"gatsby-source-filesystem": "^2.1.40",
"gatsby-transformer-remark": "^2.6.45",
"gatsby-transformer-sharp": "^2.3.7",
"github-slugger": "^1.2.1",
"jsx-to-string": "1.4.0",
"mdx-utils": "^0.2.0",
"prettier": "^1.19.1",
"prism-react-renderer": "^1.0.2",
"prop-types": "^15.7.2",
"react": "16.12.0",
"react-dom": "16.12.0",
"react-frame-component": "^4.1.1",
"react-helmet": "^5.2.1",
"react-instantsearch-dom": "^6.3.0",
"react-live": "^2.2.2",
"react-resizable": "^1.10.1",
"react-rnd": "^10.1.5",
"react-typography": "^0.16.19",
"remark-html": "10.0.0",
"remark-slug": "^5.1.2",
"sass": "1.23.7"
},
"keywords": [
"gatsby"
],
"license": "MIT",
"scripts": {
"build": "gatsby build",
"format": "prettier --write \"**/*.{js,jsx,json,md}\"",
"start": "gatsby develop -o",
"serve": "gatsby serve",
"clean": "gatsby clean",
"info": "gatsby info --clipboard",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
}
}
gatsby-node.js:
const path = require("path");
const remark = require("remark");
const remarkHTML = require("remark-html");
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === "Mdx") {
const description = node.frontmatter.description;
if (description) {
const parsedDescription = remark()
.use(remarkHTML)
.processSync(description)
.toString();
createNodeField({
name: 'description',
node,
value: parsedDescription
});
}
const pathArr = node.fileAbsolutePath.replace(`${__dirname}/content/`, "").replace(`.mdx`, "").split("/")
const globalNav = pathArr[0].toLowerCase()
const category = pathArr[2] && `${pathArr[1]}`.toLowerCase()
const isIndex = pathArr[pathArr.length - 1].toLowerCase() === 'index'
const path = node.fileAbsolutePath.replace(`${__dirname}/content/`, "").replace(`.mdx`, "").replace(" ", "-").replace("/index", "").toLowerCase()
createNodeField({
name: "globalNav",
node,
value: `${globalNav}`,
})
createNodeField({
name: "slug",
node,
value: `/${path}/`,
})
createNodeField({
name: "category",
node,
value: category,
})
createNodeField({
name: "path",
node,
value: `/${path}/`,
})
createNodeField({
name: "isIndex",
node,
value: isIndex,
})
}
}
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type MdxFrontmatterLinks implements Node @dontInfer {
github: String
figma: String
storybook: String
}
type MdxFrontmatter implements Node {
hidden: Boolean
keywords: [String]
tags: [String]
tabs: [String]
component: String
description: String
linkTo: String
redirect_from: [String]
pageOrder: [String]
}
`
createTypes(typeDefs)
}
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const result = await graphql(`
{
allMdx {
edges {
node {
id
fileAbsolutePath
fields {
globalNav
category
path
isIndex
}
frontmatter {
title
description
hidden
keywords
tags
component
linkTo
}
}
}
}
}
`)
if (result.errors) {
reporter.panicOnBuild('馃毃 ERROR: Loading "createPages" query')
}
result.data.allMdx.edges.forEach(item => {
createPage({
path: item.node.fields.path,
component: path.resolve(`./src/templates/docs.js`),
context: {
id: item.node.id,
title: item.node.frontmatter.title,
parent: item.node.frontmatter.component ? item.node.frontmatter.component : item.node.frontmatter.title,
isIndex: item.node.fields.isIndex,
globalNav: item.node.fields.globalNav,
category: item.node.fields.category,
hidden: item.node.frontmatter.hidden,
keywords: item.node.frontmatter.keywords,
tags: item.node.frontmatter.tags,
linkTo: item.node.frontmatter.linkTo,
},
})
})
}
// Allow importing components into MDX content
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
}
});
};
gatsby-browser.js:
/**
* Implement Gatsby's Browser APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/browser-apis/
*/
// You can delete this file if you're not using it
import React from 'react';
import { MDXProvider } from "@mdx-js/react";
import "./src/styles/styles.scss";
import '@servicetitan/anvil-fonts/dist/css/anvil-fonts.css';
import '@servicetitan/design-system/dist/system.css';
import { Block, Table } from './src/components';
import { Text } from '@servicetitan/design-system';
import Docs from "./src/templates/docs";
import { Link } from 'gatsby';
const components = {
pre: props => <Block {...props} />,
inlineCode: props => <code className="DocsInlineCode" {...props} />,
p: props => <span><Text {...props} className="DocsText m-b-2" style={{maxWidth:`35em`}} /></span>,
h1: props => <Text {...props} size='5' bold el='h2' />,
h2: props => <Text {...props} size='4' bold el='h3' />,
h3: props => <Text {...props} size='3' bold el='h4' />,
ul: props => <ul className="DocsList" {...props} style={{maxWidth: `35em`}} />,
li: props => <Text el="li" {...props} />,
a: props => <Link {...props} to={props.href}/>,
table: props => <Table {...props} />,
blockquote: props => <blockquote className="DocsBlockquote" {...props} />,
...require('@servicetitan/design-system'),
}
export const wrapRootElement = ({element}) => {
return (
<MDXProvider components={components}>
{element}
</MDXProvider>
)
}
export const wrapPageElement = ({element, props}) => {
return (
<Docs {...props}>{element}</Docs>
)
}
const scrollToElement = hash => {
const id = window.decodeURI(hash.replace(`#`, ``))
if (id !== ``) {
const element = document.getElementById(id)
if (element) {
window.scrollTo(0, element.offsetTop - 56 - 44)
}
}
return null
}
export const onRouteUpdate = ({ location }) => {
scrollToElement(location.hash)
return false
}
export const shouldUpdateScroll = ({ prevRouterProps: { preLocation }, routerProps: { location } }) => {
if(location.hash) {
scrollToElement(location.hash);
} else {
window.scrollTo(0, 0)
}
return null
};
gatsby-ssr.js: N/A
I believe every link in Gatsby is generated as a root-relative link. Lots of info here.
This should still work though. Are you seeing the correct behavior?
@herecydev
Well.. I'm expecting that when I am in the page /components/layouts/card and the header is #basic-card I expect either the anchor link to be relative or have the full path(/components/layouts/card#basic-card).
Is my expectation wrong?
If I just get /#basic-card it will take me to www.mysite.com/#basic-card not www.mysite.com/components/layouts/card#basic-card
Yer I think it's a general expectation (and rightfully so if you're programming directly in html). But unfortunately I don't think that'll work in this case.
Surely the simplest way to achieve what you want is to use a standard <a> element? <Link> is super useful and recommended if you're going between gatsby pages but in this case I can't think of a reason that using a hash link would be bad
If you need to go between pages i.e. /components/layouts/card to /components/layouts/othercard then you will need a different strategy.
Ah sorry, I think I totally missed mentioning that this is happening to gatsby-remark-autolink-headers 's auto generated anchor.
@tounsoo Continuing the conversation from spectrum...
I created my own little starter repo to test this, and all of my header links get created correctly. AFAICT, the gatsby-remark-autolink-headers code is correct... there is nothing in there that would cause it to prefix # with /... so possibly it's happening in gatsby-plugin-mdx. That's a completely different animal, so I need to have something that is broken, so I can do some debugging on it.
I think a simplified repo would be very helpful in this case. It's a great deal of work for me to try to replicate what you have without be able to see it. I can't just copy/paste your config files and hope everything works... I would have to stub out a lot of stuff, and also rip out a bunch that breaks.
You already know how your project is structured, so it is much easier for you to break it down and start building it up by layers until it stops working correctly. Sometimes, that process will reveal the issue all by itself.
Okay, I just noticed something...
You're replacing the a element with a gatsby-link. Look at the way gatsby-link processes the to argument:
https://github.com/gatsbyjs/gatsby/blob/70a6857aceafe4996c46e77fd58c4b2e2f586207/packages/gatsby-link/src/index.js#L142
withPrefix:
https://github.com/gatsbyjs/gatsby/blob/70a6857aceafe4996c46e77fd58c4b2e2f586207/packages/gatsby-link/src/index.js#L9-L16
That would be why your paths are being prefixed. __BASE_PATH__ gets set here:
https://github.com/gatsbyjs/gatsby/blob/e4dae4d6a46fe9ba1c3fb5398d8569e657553bd3/packages/gatsby/src/utils/webpack.config.js#L192-L198
So, it defaults to an empty string. Which, in withPrefix equals a /
Sorry for late response, I think you are on to something. I will try it out first thing tomorrow morning and if it doesnt work out, I will prepare a branch for us to debug.
Thank you so much for looking into this!
@Js-Brecht that worked!!!! you are awesome! I was totally not looking at that direction. You saved me so much time 鉂わ笍
Glad to hear! :+1:
I am closing this as resolved
@tounsoo how you solved and fixed your problem?
@muescha I removed the replacing.
As @Js-Brecht mentioned, I replacing <a> with gatsby's <Link> component in my gatsby-browser.js. Because <Link> always adds / as a base path, the hash link added by autoheader was also getting the base path from it.
One issue I just thought about... if you do want links that point to other internal routes, they won't navigate properly.
Might need to do something like this (pseudocode):
a: props => {
if (props.href.startsWith('#')) {
return <a href={props.href}>props.label</a>
} else {
return <Link {...props} to={props.href}/>
}
}
You can also set the base path with - did you tried this?
https://www.gatsbyjs.org/docs/path-prefix/~~
@muescha Did not try that. Can I pass empty prefix as well?
@tounsoo sorry wrong idea from my side - i though it wrong as you will deploy it on a subpath.
@Js-Brecht maybe also check if it is not an local site link, then also use an a link for this external links
This is a little bit more robust. TBH, this seems like something gatsby-link should do itself... but maybe the intent was to keep the purpose/usage of gatsby-link strictly for internal routing? This also seems like logic that would have to be duplicated over and over, in different projects... I wouldn't be surprised if there's a package already out there that serves this purpose, but maybe it would make sense to include a new Gatsby module that people could use, but also makes it very clear that it is for creating internal _and_ external links
import React from 'react';
import { PropTypes } from 'prop-types';
import { useStaticQuery, graphql } from 'gatsby';
import GatsbyLink from 'gatsby-link';
const DOMAIN_PATTERN = /^(?:https?:)?[/]{2,}([^/]+)/;
const HASH_PATTERN = /^#.*/;
const INTERNAL_PATTERN = /^\/(?!\/)/;
const FILE_PATTERN = /.*[/](.+\.[^/]+?)([/].*?)?([#?].*)?$/;
const getDomain = (href) => {
const matches = DOMAIN_PATTERN.exec(href);
return matches ? matches[1] : ""
}
const isHashHref = (href) => HASH_PATTERN.test(href);
const isFileHref = (href) => {
const matches = FILE_PATTERN.exec(href);
if (!matches) return false;
if (matches[1]) {
// Files won't have additional path segments following them
if (matches[2] && /[^/]/.test(matches[2])) return false;
return true;
}
return false;
}
const isInternalHref = (href, siteUrl) => {
if (INTERNAL_PATTERN.test(href)) return true;
const targetDomain = getDomain(href);
const localDomain = targetDomain && siteUrl && getDomain(siteUrl);
return localDomain && targetDomain === localDomain;
}
export const Link = (props) => {
const { site: { siteMetadata: { siteUrl } } } = useStaticQuery(graphql`
query {
site {
siteMetadata {
siteUrl
}
}
}
`)
const { to } = props;
const isHash = isHashHref(to);
const isFile = !isHash && isFileHref(to);
const isInternal = !isFile && !isHash && isInternalHref(to, siteUrl);
if (isHash || isFile || !isInternal) {
return <a {...props} href={to} />;
} else {
return <GatsbyLink {...props} />
}
}
Link.propTypes = {
to: PropTypes.string.isRequired,
}
EDIT: Forgot that host isn't available during SSR. the component could be altered to accept a parameter that defines the siteUrl, but as it is, the following config would be required:
// gatsby-config.js
// Can do something like this
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
siteMetadata: {
siteUrl: isDev ? `https://localhost/` : `https://foobar.com`
}
}
馃憤
Most helpful comment
This is a little bit more robust. TBH, this seems like something
gatsby-linkshould do itself... but maybe the intent was to keep the purpose/usage ofgatsby-linkstrictly for internal routing? This also seems like logic that would have to be duplicated over and over, in different projects... I wouldn't be surprised if there's a package already out there that serves this purpose, but maybe it would make sense to include a new Gatsby module that people could use, but also makes it very clear that it is for creating internal _and_ external linksEDIT: Forgot that
hostisn't available during SSR. the component could be altered to accept a parameter that defines thesiteUrl, but as it is, the following config would be required: