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

Feature request: Basepath support #4998

Closed
tomaswitek opened this issue Aug 21, 2018 · 74 comments
Closed

Feature request: Basepath support #4998

tomaswitek opened this issue Aug 21, 2018 · 74 comments
Assignees
Labels
kind: story type: needs investigation
Milestone

Comments

@tomaswitek
Copy link
Contributor

@tomaswitek tomaswitek commented Aug 21, 2018

Feature request

Is your feature request related to a problem? Please describe.

Multi zones is a great feature which allows to run multiple next.js apps on the same domain, but it doesn't allow to define a basepath which will be accepted by all parts of next.js. Since we are not able to namespace apps right now it is not possible to have the same names for pages in various apps.

Describe the solution you'd like

I want to be able to configure a basepath in the next.config.js file. Thanks to this configuration all parts of next.js (Router, Link, Static assets etc.) will be aware of the basepath and will automatically generate and match to the correct paths.

Describe alternatives you've considered

One alternative is to nest all desired pages into a folder which matches the basepath. This solves just one small issue with routing and is quite ugly because most of the my basepaths are not one level paths.
The second alterantive is to configure a proxy in a way where the basepath is automatically removed before the request arrives into a next.js app and also implement a custom Link component which automatically adds basepath to all links. I just don't want to maintain custom fork of next.js. It doesn't make sense in my opinion.

Additional context

The assetPrefix solution allows us to define a different prefix for each app. But as fair as I know it works only with different hosts.

with-zones example

module.exports = {
  assetPrefix: NOW_URL ? `https://${alias}` : 'http://localhost:4000'
}

If I add a basepath to it everything fails

module.exports = {
  assetPrefix: NOW_URL ? `https://${alias}/account` : 'http://localhost:4000/account'
}

screen shot 2018-08-21 at 10 47 08

In my opinion we should split it into 2 variables:

module.exports = {
  assetPrefix: NOW_URL ? `https://${alias}` : 'http://localhost:4000',
  basepath: '/account'
}

Related issues

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Aug 21, 2018

cc @jxnblk

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Aug 21, 2018

cc @alexindigo @DullReferenceException

Would love to have your feedback 👍

@tomaswitek
Copy link
Contributor Author

@tomaswitek tomaswitek commented Aug 22, 2018

After playing with the code I realized that it would be much easier to split assetPrefix into multiple parts:

module.exports = {
  host: NOW_URL ? `https://${alias}` : 'http://localhost:3000',
  basePath: '/account',
}

We can still keep the assetPrefix variable internaly, but the user should define more precisely what he needs.

For the asset part is really ok to provide these two variables together.
For routing etc we need them separately.
Or maybe we can even provide it together in a config file and then split it in the next.js codebase. In this case assetPrefix is not the right name I am afraid.

As a sideffect this also leads to less code changes.
It's quite obvious if you compare those two PRs:
panter#2 (split)
panter#1 (pass both)

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Aug 22, 2018

In my opinion, they should be separate, the reason for this is that it's not breaking and more flexible to keep assetPrefix and have basePath seperately.

@tomaswitek
Copy link
Contributor Author

@tomaswitek tomaswitek commented Aug 22, 2018

Is assetPrefix the right name then? Both variables are actually a prefix right?

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Aug 22, 2018

assetPrefix is for assets eg: the page bundles. basePath will be for the router.

The way it should work is:

  • if assetPrefix is defined use assetPrefix to load bundles, don't touch the router (current behavior)
  • if assetPrefix and basePath are provided use assetPrefix to load bundles, add basePath to router
  • if assetPrefix is not defined and basePath is, use basePath to load bundles, and add basePath to router
  • if neither assetPrefix nor basePath is defined we do nothing different (current behavior when assetPrefix is not provided)

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Aug 22, 2018

cc @alexindigo @DullReferenceException @3rd-Eden

Could you give feedback on the above proposal: #4998 (comment)

@alexindigo
Copy link
Contributor

@alexindigo alexindigo commented Aug 22, 2018

@tomaswitek Not sure what exactly didn't work for you with current assetPrefix, this is asset prefix we're using in production: "assetPrefix":"https://static.trulia-cdn.com/javascript" and it works as expected.

@alexindigo
Copy link
Contributor

@alexindigo alexindigo commented Aug 22, 2018

And in general, we're using multiple zones (we call them islands) on the same domain and "basePathing" each island never came to our minds, since it'd complicate interoperability between the islands. Let me elaborate on tat a little bit more:

So we have two islands A and B, and the main idea with it is transparency for the users that navigate island to island as part of their one-website experience. So there should be links between the islands. Then there is deployment concern vs. application concern.

  1. Deployment concern vs. application concern – application has no idea where it could be deployed, it just know how to handle incoming http requests – it has set out routes it can respond to.
    When it's deployed somewhere – it could be different domains, different ports, and yes theoretically it could be different basePath, that will be made transparent for the app via proxy or other means.

  2. Cross links between the islands – to keep the spirit of the islands as a separate deployable entities there shouldn't be any internal implementation knowledge leaks between different islands.
    So best way for islands to reference pages of each other, is for them to export available routes for other island(s) to consume (and in the nextjs world it looks like custom <IslandALink> kind of components would be a preferred way).
    So far it's all straight forward – all islands assume to share the same domain and have their set of absolute paths (/path1, path2, etc). That way second island imports that list of paths and relies on it to be stable. In the same time it's pretty minimal requirement for each island to keep their paths backward compatible (which is good thing in web anyway) :)

When we add deployment specific basePath, we automatically increase complexity of the whole system – should each island know (and maybe dictate) it's own deployment basePath? Then how is it different from the way thing work currently? Or should island A be agnostic of it's deployment path? Then how island B will find deployed island A, since it only knows what island A knows about itself? Or you'd have to supply basePath for all the deployed island to all other islands? And with modern way off deploying things, it means redeploying all the islands when you need to add new one.

Or how you envisioned that part of the story?

Thank you.

@alexindigo
Copy link
Contributor

@alexindigo alexindigo commented Aug 22, 2018

^ it was written before morning coffee, so please let me know if you need more coherent explanation for any parts of it. :)

@tomaswitek
Copy link
Contributor Author

@tomaswitek tomaswitek commented Aug 22, 2018

First of all thank you guys that you took the time for reviewing my issue.

@timneutkens Yes assetPrefix has priority over basePath, that is exactly what we've discussed at the beginning. After I saw how many files I had to change I thought the second way would be cleaner. But I'll rollback to the first solution. Let's keep it totally separate, no problem at all. I was just loudly thinking.

@alexindigo Thx for your detailed answer. Let me try to answer your questions 😏

Not sure what exactly didn't work for you with current assetPrefix

I have two problems here:

  1. I can't work with multiple domains nor subdomains in the current project. (Domain restrictions and no wildcard SSL certificate)
  2. The current implementation of assetPrefix on a single domain requires more adjustments in proxy routing, static files etc. We could reduce this adjustments by introducing basePath. It won't brake anything and it won't increase complexity because you don't have to provide the basePath as @timneutkens already mentioned.

application has no idea where it could be deployed

We have the same goal here of course! We are defining assetPrefixes dynamically in the current solution we have. It is provided via request headers by proxy.

Then how is it different from the way thing work currently?

Router will be aware of contextPath and will reduce the amount of custom code.

should each island know (and maybe dictate) it's own deployment basePath? Or should island A be agnostic of it's deployment path?

It doesn't have to be. The developer should have freedom here. It should be possible to provide basePath dynamically the same way as assetPrefix.

Then how island B will find deployed island A, since it only knows what island A knows about itself? Or you'd have to supply basePath for all the deployed island to all other islands? And with modern way off deploying things, it means redeploying all the islands when you need to add new one.

Maybe you could also add the basePath into the routes export. I don't know. I am not saying that the basePath variable is important for every use case. It looks like it's not the best solution for you. But that's totally fine. The thing is that you can still use just assetPrefix and nothing will change for your islands. It looks like you have your own routing anyway. Cross links between zones is not even important for our project, our zones are really independent and isolated from each other.

And with modern way off deploying things, it means redeploying all the islands when you need to add new one.

I don't see a reason why. I can even imagine that some zones have basePaths and some not. And maybe some apps will use the basePath config even without multi zones setup.

@tomaswitek
Copy link
Contributor Author

@tomaswitek tomaswitek commented Aug 22, 2018

@alexindigo could you pls provide us with a two real island urls, which are rendered by next.js so I could see it in action? I tried to find one, but could't find a page on your domain with _next requests 😄
Do all of your islands have the same configuration?
"assetPrefix":"https://static.trulia-cdn.com/javascript"

@alexindigo
Copy link
Contributor

@alexindigo alexindigo commented Aug 23, 2018

@tomaswitek

I can't work with multiple domains nor subdomains in the current project. (Domain restrictions and no wildcard SSL certificate)

Oh, so you don't use CDN in the classical sense, but rely on assets being fetched from each app directly? I see.

The current implementation of assetPrefix on a single domain requires more adjustments in proxy routing, static files etc. We could reduce this adjustments by introducing basePath. It won't brake anything and it won't increase complexity because you don't have to provide the basePath as @timneutkens already mentioned.

Btw, it wasn't "no, don't add that feature" :) It was more like – "Probably we can think about this approach more holistically" :)

It doesn't have to be. The developer should have freedom here. It should be possible to provide basePath dynamically the same way as assetPrefix.

Yes. It only works though when there is no linking between the islands. And sounds like this is your use case. In the same time, I'm having hard time understanding what makes them islands instead of just being a bunch of standalone applications then, if they're 100% independent? :)

Maybe you could also add the basePath into the routes export.

I don't se how it could be done (easily), since routes export happens at build time, and basePath being defined at deployment time, and there could be more than one deployment of the same code artifact (stage, preprod, prod, testing env, etc).


Do all of your islands have the same configuration?
"assetPrefix":"https://static.trulia-cdn.com/javascript"

Yes, all islands share their assets, since next does content hashing, it's not only non-issue, but actually very beneficial. (We extract built assets from each artifact and publish on CDN at deployment time).

And that way we have only "regular html" requests to our app servers, this is why I won't see any "_next" paths on trulia.com

As for the islands examples:

Our fresh brand new island – Neighborhoods page – https://www.trulia.com/n/ca/san-francisco/pacific-heights/81571 (and you can find more of them here: http://www.trulia.com/neighborhoods)
This island is responsible for all /n/* paths.

And another island is our login page – https://login.trulia.com/login – it looks like different domain, but it's really not, it looks that way for different set of reasons, but technically it's the same deployment. :)
And this island handles urls like /login, /signup.

Let me know if you have more questions.

@tomaswitek
Copy link
Contributor Author

@tomaswitek tomaswitek commented Aug 23, 2018

@alexindigo thank you very much for your examples.
I have a few questions after anaylzing the examples 😄

You still do server rendering for every island, but you try to extract as much possibles assets into a common CDN right?

Can you pls describe a little bit more what exactly happens when https://www.trulia.com/n/ca/san-francisco/pacific-heights/81571 is called? Does your proxy know that /n stands for neighborhood overview and forwards it to the right island? Does it somehow affects the request before it arrives at the island?

Do you use built-in routing from next inside an island or do you have a custom solution?
I wanted to check the routing inside your island. Unfortunately Neighborhood overview has more or less just modal navigation without changing the url. In Login there seems to be a completely custom solution.

@tomaswitek
Copy link
Contributor Author

@tomaswitek tomaswitek commented Aug 23, 2018

I hope I'll answer all your questions in this comment 😏

Btw, it wasn't "no, don't add that feature" :) It was more like – "Probably we can think about this approach more holistically" :)

Sure, it would be great to find a solution where I don't have to touch next.js 😏

Yes. It only works though when there is no linking between the islands. And sounds like this is your use case. In the same time, I'm having hard time understanding what makes them islands instead of just being a bunch of standalone applications then, if they're 100% independent? :)

I never wrote nor said that I search for an "island" solution. I just had a chat with @timneutkens where I described my problem and Tim's answer was basically next.js does not support basepaths. And after googling a little bit I realized I am not the only one searching for it. So I thought I could contribute a little bit. Afterwards Tim pinged you to give a me a feedback and I am very thankful for your feedback.

I don't se how it could be done (easily), since routes export happens at build time, and basePath being defined at deployment time, and there could be more than one deployment of the same code artifact (stage, preprod, prod, testing env, etc).

Well if you want to export routes at build time and make them available for other islands then the only straightforward way is probably to hardcode the basePath in the config. I get your point. On the other side, is that really such a problem? You could still deploy the app to different domains and ports and you could use the same basePath for each env.

@alexindigo
Copy link
Contributor

@alexindigo alexindigo commented Aug 24, 2018

Good morning @tomaswitek :)

My experience with the "basePath" functionality, that it very deceiving in it's complexity, and it's usually better to implement that kind of things without rushing into it with one specific problem,
but looking at it from multiple angles. Similar to how you'd approach deep merging – outline multiple use cases and see how (and if) they all fall under one umbrella. Since having incompatible features between (even major) versions of the framework is very annoying :)

You could still deploy the app to different domains and ports and you could use the same basePath for each env.

Sounds like you'd be ok with the solution where that "basePath" is part of your routing code, something that you mentioned in the beginning – like subfolder inside pages directory (btw, that approach would signal to developers chosen basePath pretty well). But the only thing that stopped you is that internal nextjs path for assets _next isn't configurable.

And that sounds like more narrow problem we can solve with less long term side effects.

And it could bring us even further, like if we can configure assetPath per asset (e.g. with next.config map of some sort) – it will allow us to have shared assets between the apps, which will improve performance, and other things.

And there is open PR for that feature. ;) /cc @timneutkens sounds like it's time to get back to that puppy. :)

@ccarse
Copy link

@ccarse ccarse commented Jan 15, 2019

If you're not going to add this any time soon, could we get an example express based server.js added to the readme that does this and works? I've tried a few that have been floating around in these issues but couldn't get them to work. Thanks.

@tomaswitek
Copy link
Contributor Author

@tomaswitek tomaswitek commented Jan 15, 2019

Hi @ccarse I have a working fork which we use in production already: panter#2
I am also ready to invest time to open a PR for this feature.
@timneutkens @alexindigo is there a other way how to solve this problem?
If we don't need a basePath config, can you pls give us a minimal example using assetPath ?

@manovotny
Copy link
Contributor

@manovotny manovotny commented Feb 15, 2019

My company is also coming up against this.

We're slowly taking over a legacy app, section by section, and replacing it with Next.js.

As a simplified example:

URL App
example.com legacy
example.com/shop next
example.com/search legacy
example.com/members next

That means we want everything to be prefixed within each Next.js app... Pages, routes, assets, etc.

It's also worth noting that we're not using Now, so we cannot take advantage of now.json routing. We have our own load balancer sitting out in front of the whole domain and then routing traffic based on subpath.

We're also using a custom server (hapi), so it'd be nice if we could leverage whatever is create here within a custom server too.

Maybe there's some combination of now.config.json settings or some use of micro-proxy we can use to accomplish this same thing, but we haven't figured out the right combination yet.

@Zertz
Copy link
Contributor

@Zertz Zertz commented Feb 16, 2019

We're running into, I think, the same problem with multiple statically exported Next.js apps hosted on Now v2.

URL App
example.com next
example.com/dashboard next

As expected, the root app works just fine. Things go awry in the second one though. We're currently wrapping next/link which, combined with assetPrefix, solves most of the problem:

export default ({ children, href, ...rest }) => (
      <Link href={process.env.NODE_ENV === "production" ? `/dashboard${href}` : href} {...rest}>
        {children}
      </Link>
);

However, this breaks prefetch because then it tries to look for .js files at the wrong URL:

Our current workaround is to disable prefetch, which isn't ideal.

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Mar 30, 2020

Any update on this... last year around this time i ran into this issue. Now a year later, i'm working on a new app and have to do the same workarounds i did last year... kinda alarming for a 'production-ready' react fw. Basepaths should be a vanilla feature.

I'm not sure what you're expecting by posting this.

Next.js is being worked on full-time by my team (5 people), and we're working on many features at the same time. In the past year we've worked on these:

Effectively making Next.js applications (new and existing) significantly smaller, faster and more scalable.

If you want to voice your "upvote" for a feature you can. use the 👍 feature on the initial thread.

I definitely agree basePath should be a built-in feature. It's on the roadmap already and I even wrote an initial PR, which you could have seen by reading back on the thread.

Here's the PR: #9872

Feel free to reach out to enterprise@vercel.com if you want to financially contribute to making this feature happen.

RyanPridgeon added a commit to RyanPridgeon/house-of-cards-next-js-practise that referenced this issue Apr 3, 2020
@Sletheren
Copy link

@Sletheren Sletheren commented May 27, 2020

What is the Status on this? we are really depending on this :/

@martpie
Copy link
Contributor

@martpie martpie commented May 27, 2020

@Sletheren basePath support is experimental right now, use at your owm risks.

cf. #9872

@Sletheren
Copy link

@Sletheren Sletheren commented May 27, 2020

@Sletheren basePath support is experimental right now, use at your own risks.

cf. #9872

@martpie I already saw it, but for. my case basePath is not just one, it can be multiple basePath, since we serve our app through different "URLs" and setting up basePath during build time is not an option (even though it has to support an array of paths rather than a single string)

@pe-s
Copy link

@pe-s pe-s commented Jun 13, 2020

@timneutkens Thanks for the update. Would you be so kind to give another update. This is for us a key feature and we need to know...

  1. Will this be an enterprise-only (your reference to contact enterprise sales caused some irritation)?

  2. It seems to be on the roadmap, according to the PR it won't be removed again; can you give some indication if it's safe to build around this feature now without getting any surprises in the next months like a crippled open source version and another one with full support after we negotiated weeks with some random sales guys about arbitrary prices?

I understand that you guys work on many features and everyone has his/her priorities but even smaller setups need to proxy Next, run multiple instances and give it a dedicated basePath per service. Before we now start to build multiple services on Next we need to know how probable and soon this feature is available as full open source. Otherwise it would be just too risky invest further time into Next.

Thanks for your understanding and looking fwd to your feedback.

@pe-s
Copy link

@pe-s pe-s commented Jun 13, 2020

FWIW, I got it now working and for others driving by:

Put this in your next.config.js:

module.exports = {
  experimental: {
    basePath: '/custom',
  },
}

Then, I needed to restart the server and to setup my web server middleware properly:

I catch all requests via a custom path, eg. app.use('/custom', (req, res...) => { ... and then (which was important) I need to proxy to the URL of the system where Next is running (so the internal address of your container orchestration and again with the respective path if you use http-proxy => eg. ... target: 'http://next:3000/custom), so not just the host without the custom path. If you use http-proxy-middleware you do not need this.

It feels quite ok, I hope that this feature won't need any EE license. If your team needs any help to get this feature mature, pls let us know, maybe we can help!

Edit: Just tried this also in with Next's production mode and it seems to work as well.

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Jun 14, 2020

@timneutkens Thanks for the update. Would you be so kind to give another update. This is for us a key feature and we need to know...

  1. Will this be an enterprise-only (your reference to contact enterprise sales caused some irritation)?
  2. It seems to be on the roadmap, according to the PR it won't be removed again; can you give some indication if it's safe to build around this feature now without getting any surprises in the next months like a crippled open source version and another one with full support after we negotiated weeks with some random sales guys about arbitrary prices?

I understand that you guys work on many features and everyone has his/her priorities but even smaller setups need to proxy Next, run multiple instances and give it a dedicated basePath per service. Before we now start to build multiple services on Next we need to know how probable and soon this feature is available as full open source. Otherwise it would be just too risky invest further time into Next.

Thanks for your understanding and looking fwd to your feedback.

@pe-s I think you're misunderstanding my post.

There is no "enterprise Next.js version" as of now. I was referring to the numerous occasions where external companies reached out to pay for consulting to build out features like this one in a shorter timespan. E.g. zones support was built in collaboration with Trulia.

This feature is being worked on still and is on the roadmap. All features being worked on are open-source, like I said there's no enterprise version of Next.js. We have multiple priorities of high-impact work on the roadmap though hence why I referred to contacting enterprise@vercel.com if you need this feature as soon as possible / to discuss enterprise support for Next.js.

@pe-s
Copy link

@pe-s pe-s commented Jun 15, 2020

@timneutkens tx for your quick response and great! Then, we can go all in :)
Keep up the great work!

@Timer Timer added the kind: story label Jun 17, 2020
@Timer Timer added this to the iteration 3 milestone Jun 17, 2020
@timneutkens
Copy link
Member

@timneutkens timneutkens commented Jun 26, 2020

Basepath support is out on next@canary right now, it's no longer experimental. It will be on the stable channel soon.

@mohsen1
Copy link
Contributor

@mohsen1 mohsen1 commented Jun 29, 2020

I'm pretty late to this but did you consider using actual HTML <base> instead of manually handling this?

@peetjvv
Copy link

@peetjvv peetjvv commented Jun 30, 2020

Basepath support is out on next@canary right now, it's no longer experimental. It will be on the stable channel soon.

@timneutkens, thank you for this addition. Do you know when the non-experimental basePath support will be officially released?

Also, when I set the basePath the assets (located in the public folder) gets served to the appropriate url as expected. But, when I reference them in my code then I have to add the base path to the src manually, because otherwise they will still be referenced from the normal path. Is this the expected use of basePath? I have also tried using assetPrefix, but it didn't have any effect to my code that I could tell.

Example:

  1. using next v9.4.5-canary.24
  2. basePath set to /alerts in next.config.js:
const basePath = '/alerts';
module.exports = {
  basePath: basePath,
  env: {
    BASE_PATH: basePath,
  },
};
  1. asset located in public/images/example.png
  2. example use of asset in react component:
const ExampleImage = () => (
  <img src={`${process.env.BASE_PATH}/images/example.png`} />
);

@kmturley
Copy link
Contributor

@kmturley kmturley commented Jul 10, 2020

In my tests, it's not updating assets urls.

I installed the latest canary:
npm install next@9.4.5-canary.31

next.config.js

const isProd = process.env.NODE_ENV === 'production';

module.exports = {
  basePath: isProd ? '/example' : ''
}

All pages and links load correctly:
http://localhost:3000/example/posts/pre-rendering
http://localhost:3000/example/posts/ssg-ssr
http://localhost:3000/example/posts/pre-rendering

But images, favicons etc are not mapped:
http://localhost:3000/favicon.ico 404
http://localhost:3000/images/profile.jpg 404

Did anyone test this? I also tried using assetPrefix, but that didn't work either.

In addition i'm confused, why not use the built in browser functionality for this?
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

@peetjvv
Copy link

@peetjvv peetjvv commented Jul 10, 2020

Thank you for looking into this on your end as well @kmturley . Glad to know it's not just me.
@timneutkens , should we reopen this issue / create a new issue for this bug?

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Jul 12, 2020

You have to prefix images manually. You can get the basePath using

const {basePath} = useRouter()

@github0013
Copy link

@github0013 github0013 commented Jul 13, 2020

https://nextjs.org/docs/api-reference/next.config.js/cdn-support-with-asset-prefix

Next.js will automatically use your prefix in the scripts it loads, but this has no effect whatsoever on the public folder;

Now, I come to realize there are multiple ways to link to files in /public. e.g. <img/> <link/> ...
Is this why we have to manually specify the basePath to the each?

If there was a component like below available, I think it would save time and reduce confusions for a lot of people?

<WithinBasePath>
  {/* automatically fixes the path with basePath */}
  <img src="/logo.png" />
</WithinBasePath>

@github0013
Copy link

@github0013 github0013 commented Jul 13, 2020

I really don't think this is appropriate, but this is what I meant.

// src/components/WithinBasePath/index.tsx

import React from "react"
import path from "path"
import { useRouter } from "next/router"
interface Props {}

const WithinBasePath: React.FC<Props> = (props) => {
  const { basePath } = useRouter()
  const children = [props.children].flatMap((c) => c) as React.ReactElement[]
  return (
    <>
      {children.map((child, key) => {
        let newChild = null

        switch (child.type) {
          case "img":
            newChild = React.createElement(child.type, {
              ...child.props,
              src: path.join(basePath, child.props.src),
              key,
            })
            break
          case "link":
            newChild = React.createElement(child.type, {
              ...child.props,
              href: path.join(basePath, child.props.href),
              key,
            })
            break
          default:
            newChild = React.createElement(child.type, {
              ...child.props,
              key,
            })
        }
        return newChild
      })}
    </>
  )
}
export default WithinBasePath
// pages/test.tsx

import React from "react"
import WithinBasePath from "@src/components/WithinBasePath"
interface Props {}

const test: React.FC<Props> = (props) => {
  return (
    <WithinBasePath>
      <img src="/123.jpg" />
      <link href="/abc.jpg" />
      <div>other element</div>
    </WithinBasePath>
  )
}
export default test

@kmturley
Copy link
Contributor

@kmturley kmturley commented Jul 18, 2020

For those trying use const {basePath} = useRouter() which is a Hook, to work with Classes and Components and getting this error:

Invalid Hook Call Warning

https://reactjs.org/warnings/invalid-hook-call-warning.html

You can get it working using:

import { withRouter, Router } from 'next/router'

class Example extends Component<{router: Router}, {router: Router}> {
  constructor(props) {
    super(props)
    this.state = {
      router: props.router
    }
  }
  render() {
    return (
      <Layout home>
        <Head><title>Example title</title></Head>
        <img src={`${this.state.router.basePath}/images/creators.jpg`} />
      </Layout>
    )
  }
}
export default withRouter(Example)

@kmturley
Copy link
Contributor

@kmturley kmturley commented Jul 28, 2020

If you want to use basePath with markdown, it looks like you need to do a find and replace in the string:

const content = this.state.doc.content.replace('/docs', `${this.state.router.basePath}/docs`);
return (
<Layout>
  <Container docs={this.state.allDocs}>
    <h1>{this.state.doc.title}</h1>
    <div
      className={markdownStyles['markdown']}
      dangerouslySetInnerHTML={{ __html: content }}
    />
  </Container>
</Layout>
)

@peetjvv
Copy link

@peetjvv peetjvv commented Jul 28, 2020

You have to prefix images manually. You can get the basePath using

const {basePath} = useRouter()

This solution doesn't take images imported in a css or scss file into account though. Do you have a solution for how to set the base path when importing an asset from within a css or scss file?
With this solution we will have to ensure that all images are imported either through an img tag, inline styling or in the style tag. It's not ideal, because it will split your styles to be implemented in multiple places.

@kshaa
Copy link

@kshaa kshaa commented Sep 18, 2020

@peetjvv Here's a suboptimal solution for using assets with prefixed basePaths in CSS. Create, import and add a <CSSVariables> component in _app.tsx, which injects a global inlined <style> element containing CSS variables, which you can then use all throughout your stylesheets.

E.g. at the opening of <body> build and inject variables:

<style>
:root {
      --asset-url: url("${basePath}/img/asset.png");
}
</style>

To get that basePath I use the @kmturley's approach using withRouter.
Here's how that component could look like:

import { withRouter, Router } from "next/router";
import { Component } from "react";

export interface IProps {
  router: Router;
}

class CSSVariables extends Component<IProps> {
  render() {
    const basePath = this.props.router.basePath;
    const prefixedPath = (path) => `${basePath}${path}`;
    const cssString = (value) => `\"${value}\"`;
    const cssURL = (value) => `url(${value})`;
    const cssVariable = (key, value) => `--${key}: ${value};`;
    const cssVariables = (variables) => Object.entries(variables)
      .map((entry) => cssVariable(entry[0], entry[1]))
      .join("\n");
    const cssRootVariables = (variables) => `:root {
      ${cssVariables(variables)}
    }`;

    const variables = {
      "asset-url": cssURL(
        cssString(prefixedPath("/img/asset.png"))
      ),
    };

    return (
      <style
        dangerouslySetInnerHTML={{
          __html: cssRootVariables(variables),
        }}
      />
    );
  }
}

export default withRouter(CSSVariables);

@balazsorban44
Copy link
Member

@balazsorban44 balazsorban44 commented Jan 29, 2022

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
kind: story type: needs investigation
Projects
None yet
Development

No branches or pull requests