Maintaining relative in-content URLs in Ghost v3 API

I’m ready to go live with my gatsby front-end.

My url is, it’s a Ghost 3.7 running on a DO droplet. Gatsby is on Github and deployed to Netlify.

Right now the CNAME in my DNS provider goes to a subdomain ( The theme is a mix of my own code + someone professional who could do some of the graphql piping (whew).

To migrate so my Gatsby-generated site is retrieved at I know i have to change

  1. Netlify’s domain setting - switch from to
  2. DNS settings (in Cloudflare for me) - turn off the * and www entry for, and change the CNAME from beta. to the base website
  3. Ghost: set the site to private

Problem with this is - how will I then access my /admin to update posts?

What’s the cleanest way? Is it to change my ghost config url to something like admin.*?

I’d rather image URLs stay the same, because of SEO, that’s all.

Ghost has a configurable admin url, you can run ghost config set admin.url and access your panel at As far as I’m aware, the admin and Ghost URL have to be mounted on the same subdirectory, though not the same domain.

1 Like

@vikaspotluri123, you’re a legend! Thanks!

Fast and helpful replies like these are ones that make me want to go help other people.

1 Like

@vikaspotluri123 actually it turned out that didn’t work and I’m in a strange place now.

I had

The first A and ALIAS records conflicted. I couldn’t make API requests to the server for content.

So I updated the server to serve from

But now all the images are serving from

Further, when I have links in content that go e.g. to a # for a table of contents, they’re directing to

I’d really like Ghost to be accessible at any URL (I don’t care), but for it to serve all of its content/links as if they’re coming from the base url, while that webpage is still served from netlify.

Any ideas on how to do this?

It makes sense that you had an A / ALIAS conflict, since that’s essentially telling your domain to point to 2 servers at once.

I’m not entirely sure how content is served from Ghost when you have an Admin URL configured. However, if images are served from your admin url, that shouldn’t be an issue since it’s not really user-facing :thinking:

There definitely seems to be something wrong with this; Ghost should determine all URLs using the url property in your config file, or if you’re using anchors, relative to the current page.

Having all your content served 100% from Netlify is possible, but I can’t tell you exactly how to do it because I don’t use Gatsby. The tl;dr is you would need to configure Gatsby to download all of the images rather than let them be hosted elsewhere.

You are probably running into these issues of gatsby-starter-ghost:



Currently gatsby-starter-ghost is not really production ready, so I would caution against migrating your blog to Gatsby this point in time!

Thanks @styxlab.

I migrated to gatsby too quickly it seems. But it’s my personal blog, and not a terribly sensitive production environment (I get a few hundred hits a day), so I’m happy to try to make it work.

This seems like more of a problem using Ghost as a headless CMS API. It’s either a lack of a capability, or a configuration option that I haven’t uncovered.

I made an API call to my /about page (using this link if you want to see) and can see that internal links are returned as absolute, resolving to pages that don’t exist, rather than relative.

Like you mentioned in your second issue, the internal CMS doesn’t provide an option to store a link just as a relative link.

I know that in themes you can specify absolute/relative URLs:

I don’t know if that can be done as an option in the API. I have to look into this later.

This is exactly the issue I described. As far as I know, there is no option to return relative URLs that were previously set within posts or pages.

This problem can be solved in gatsby-starter-ghost but it’s not as simple as you might think, at least if you want a proper solution to it. If you are looking for a hack, I’d suggest you use a regex in your src/templates/page.js. Something like this using lodash (untested!):

const cmsUrl = _.head(_.split(page.url, page.slug, 1))
const pageHtml = _.replace(page.html, cmsUrl , `/`)
<div className="post-content load-external-scripts" 
   dangerouslySetInnerHTML={{ __html: pageHtml || page.html }} />

You would have to do that for posts, too. This is just one of many issues you will run into… I’m working on a set of plugins in an attempt to solve some of the most pressing ones that everyone will encounter when migrating from Ghost to Headless-Ghost-Gatsby. However, it will take some time until they are ready to be published.

Good, glad I’m not alone.

I’m going through and setting the right URLs in my blog posts for all the ones that are broken, anyway (the most popular posts). There’s definitely a smarter mysql database find-replace way to do this, but I break things too easily…

I think your hack would work in page.js and post.js, especially if I limited it to <a href=" strings.

I might try it… ugh. I like hacks, but I tend to forget what I previously did!

@dana I was able to solve this quicker than expected. Check out my new repo gatsby-starter-try-ghost where the linking issue is solved properly with a plugin approach. I also added PrismJS support, which will improve the current state of your Gatsby-Ghost blog.

Ah cool! But where in that repo is the plug-in support? I’ve been poking around…

Meanwhile I went in to my Ghost install and manually changed a lot of links from admin. to the correct links. It seems to work for now, focusing on the highest traffic posts… e.g. my page now links to other URLs on the base URL.

I’d like the images to be apparently served from the base URL too. Does your solution do that somehow?

On another note — I really like your base, especially your posts tiles layout! I’d use it, but unfortunately I’ve already made customisations to mine (front page, the static About post and the sign-up form) that don’t I want to make again unless I have to. I’m not very experienced at using github, version control, cloning/pushing etc. and every time I’m worried I’ll break something.

The url rewrite functionality is shipped in a plugin called

There should be no need to modify those plugins. However, if you want to customize the layout you can use a technique called shadowing where you put your own components into the src folder in the root of gatsby-starter-try-ghost. A good tutorial on this topic can be found here: Shadowing in Gatsby Themes.

Funny that you are asking about the images - that’s the next exciting feature I’m going to enhance gatsby-starter-try-ghost with.

I recognize that it doesn’t solve the root problem of being able to serve both Gatsby and Ghost simply… but here is another possible solution to the image url serving problem. I was just hearing about CloudFlare’s “workers” and came across a way to use them to change a request and response on its way through your DNS.