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

SSR asPath behavior when mounting app under a path #4792

Closed
Janpot opened this issue Jul 17, 2018 · 19 comments
Closed

SSR asPath behavior when mounting app under a path #4792

Janpot opened this issue Jul 17, 2018 · 19 comments

Comments

@Janpot
Copy link
Contributor

Janpot commented Jul 17, 2018

Bug report

Describe the bug

When mounting the next.js handler under a path asPath shows different values on server and browser.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Clone https://github.com/Janpot/next-mount-subpath
  2. npm i
  3. npm run dev
  4. Go to http://localhost:3000/test
  5. check the console

Expected behavior

No warnings

Screenshots

screen shot 2018-07-17 at 16 25 51

System information

  • OS: macOS
  • Browser: chrome
  • Version of Next.js: 6.1.1
@timneutkens
Copy link
Member

Unrelated to this issue, but you're missing:

    server.get('*', (req, res) => {
      return handle(req, res)
    })

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

Actually, I don't want to serve anything under the root. I plan to compose several of these together with micro-proxy and all of them will have the same pages.
The app is working for me without your piece of code. What is it supposed to add?

@timneutkens
Copy link
Member

Oh wait nvm you want to only server un /test 👍

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

For context, I'm just in the experimentation phase of a new i18n workflow I'd like to try.
basically

  1. I'd like to run a next.js app for each locale (the same app but with a different configuration)
  2. all of them get composed together by proxying (like zones)
  3. the next.js app webpack configuration will be augmented so it inlines i18n messages as react components (similar to this experiment)

edit:
come to think of it, I probably want to add

server.use('/static', handle)

?

@timneutkens
Copy link
Member

timneutkens commented Jul 17, 2018

It's actually a problem with express, they override req.url when using app.use

https://expressjs.com/en/api.html#req.originalUrl

This property is much like req.url; however, it retains the original request URL, allowing you to rewrite req.url freely for internal routing purposes. For example, the “mounting” feature of app.use() will rewrite req.url to strip the mount point.

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

Yes, they have req.originalUrl that we need to get back in the mix somehow

@timneutkens
Copy link
Member

So what you have to do is req.url = req.originalUrl in the handler

@timneutkens
Copy link
Member

This will also make sure Next looks for the right directory, as we don't have a client-side basepath option. Meaning your next/link wouldn't work correctly

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

I tried req.url = req.originalUrl but it doesn't work for me. next doesn't seem to recognize the internal urls anymore.

also, I have a solution for the next/link, I just need to prefix the asPath with /test. With a little context magic this becomes relatively clean

// components/link.js
import Link from 'next/link';
import React from 'react';
import PropTypes from 'prop-types';

export default class PrefixedLink extends React.Component {
  render () {
    const { href, as = href, ...props } = this.props;
    const { prefix = '' } = this.context;
    return <Link href={href} as={`${prefix}${as}`} {...props} />;
  }
}

PrefixedLink.contextTypes = {
  prefix: PropTypes.string
};

export class PathPrefix extends React.Component {
  getChildContext () {
    return { prefix: this.props.prefix || '' };
  }

  render () {
    return this.props.children;
  }
}

PathPrefix.childContextTypes = {
  prefix: PropTypes.string
};

Just make sure to set the context in _app.js and Links work everywhere

// pages/_app.js
import App, { Container } from 'next/app';
import React from 'react';
import { PathPrefix } from '../components/link';

export default class MyApp extends App {
  static async getInitialProps ({ Component, router, ctx }) {
    let pageProps = Component.getInitialProps
      ? await Component.getInitialProps(ctx)
      : {};

    return { pageProps };
  }

  render () {
    const { Component, pageProps } = this.props;
    return <Container>
      <PathPrefix prefix='/test'}>
        <Component {...pageProps} />
      </PathPrefix>
    </Container>;
  }
}

(next.js doesn't work with the new React context API)

@timneutkens
Copy link
Member

All your pages should be put into pages/test with this setup, that's the reason it doesn't work.

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

hmmm, that seems to generate some errors

GET http://localhost:3000/test/_next/-/page/test.js 404 (Not Found)

edit:
instead of req.url = req.originalUrl, seems like I can also just do

server.get(`/test*`, handle);

But that doesn't really solve the problem

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

@timneutkens Actually, the following seems to do it, when using pages/test/index.js:

    server.use('/test', (req, res) => {
      if (!/^\/(?:static|_next)\//.test(req.url)) {
        req.url = req.originalUrl;
      }
      handle(req, res);
    });

as well as

    server.get('/test*', (req, res) => {
      const nextUrl = req.url.replace(/^\/test/, '');
      if (/^\/(?:static|_next)\//.test(nextUrl)) {
        req.url = nextUrl;
      }
      handle(req, res);
    });

but tbh, it feels a bit hacky. I mean, whether using express or anything else, some url rewriting like this is always going to need to happen. Since it relies on internal API design (static|_next), it feels quite brittle.

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

In my ideal world I'd like to be able to do

const app = next({
  dev,
  conf: {
    // tell next where the app root is (so it can strip it from incoming urls on the server)
    // will also be passed to clientside router for <Link>s
    appPrefix: `/test`, 
    assetPrefix: `/test` // or a cdn
  }
});

And

server.get('/test*', app.getRequestHandler());

And the pages would reside under /pages (no subfolder equal to the path)
This would make the resulting app quite portable across paths, only need to update appPrefix.
Is that something that next.js could be interested in in working towards? (or accept PRs for)

@lucasnorman
Copy link

lucasnorman commented Jul 17, 2018

@Janpot Tried your method above and I got a 404 for all my _next files.

server.use("/auth", (req, res) => {
    if (!/^\/(?:static|_next)\//.test(req.url)) {
      req.url = req.originalUrl;
    }
    handle(req, res);
  });

server.get("/auth*", (req, res) => {
    const nextUrl = req.url.replace(/^\/auth/, "");
    if (/^\/(?:static|_next)\//.test(nextUrl)) {
        req.url = nextUrl;
    }
    handle(req, res);
});

screen shot 2018-07-17 at 11 14 35 am

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

And you've put your pages under ./pages/auth/?

@lucasnorman
Copy link

I did not. I think that is the issue... will report back once my build process finishes. Thanks!

@lucasnorman
Copy link

lucasnorman commented Jul 17, 2018

@Janpot So for localhost:3000/auth/${PATH} I get:
GET http://localhost:3000/_next/49d131b1-34ac-4d68-a8b2-a39a51266781/page/_app.js 404 (Not Found)

My pages are now inside the auth subfolder. Anything else I am missing?

@Janpot
Copy link
Contributor Author

Janpot commented Jul 17, 2018

@lucasnorman Check out this branch: https://github.com/Janpot/next-mount-subpath/tree/with-subfolder

@timneutkens Do you mean that with this solution, <Link>s are supposed to work with the right basepath?

@timneutkens
Copy link
Member

Closing this in favor of #4998

@lock lock bot locked as resolved and limited conversation to collaborators Mar 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants