Next.js: Solicitação de recurso: suporte Basepath

Criado em 21 ago. 2018  ·  73Comentários  ·  Fonte: vercel/next.js

Solicitação de recurso

Sua solicitação de recurso está relacionada a um problema? Por favor descreva.

Multi-zonas é um ótimo recurso que permite executar vários aplicativos next.js no mesmo domínio, mas não permite definir um caminho de base que será aceito por todas as partes de next.js. Como não podemos atribuir nomes a aplicativos no momento, não é possível ter os mesmos nomes para páginas em vários aplicativos.

Descreva a solução que você gostaria

Quero ser capaz de configurar um basepath no arquivo next.config.js. Graças a esta configuração, todas as partes de next.js (roteador, link, ativos estáticos, etc.) reconhecerão o caminho de base e serão automaticamente gerados e correspondentes aos caminhos corretos.

Descreva as alternativas que você considerou

Uma alternativa é aninhar todas as páginas desejadas em uma pasta que corresponda ao caminho de base. Isso resolve apenas um pequeno problema com o roteamento e é muito feio porque a maioria dos meus caminhos de base não são caminhos de um nível.
A segunda alternativa é configurar um proxy de forma que o caminho de base seja removido automaticamente antes que a solicitação chegue em um aplicativo next.js e também implementar um componente Link personalizado que adiciona automaticamente o caminho de base a todos os links. Só não quero manter o fork personalizado do next.js. Não faz sentido na minha opinião.

Contexto adicional

A solução assetPrefix nos permite definir um prefixo diferente para cada aplicativo. Mas, pelo que sei, só funciona com hosts diferentes.

exemplo com zonas

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

Se eu adicionar um caminho de base a ele, tudo falhará

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

screen shot 2018-08-21 at 10 47 08

Na minha opinião, devemos dividi-lo em 2 variáveis:

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

Assuntos relacionados

story needs investigation

Comentários muito úteis

Qualquer atualização sobre isso ... no ano passado, nessa época, me deparei com esse problema. Agora, um ano depois, estou trabalhando em um novo aplicativo e tenho que fazer as mesmas soluções alternativas que fiz no ano passado ... um pouco alarmante para uma reação "pronta para produção". Os caminhos de base devem ser uma característica comum.

Não tenho certeza do que você espera ao postar isso.

Next.js está sendo trabalhado em tempo integral pela minha equipe (5 pessoas), e estamos trabalhando em muitos recursos ao mesmo tempo. No ano passado, trabalhamos nisso:

Tornando eficazmente os aplicativos Next.js (novos e existentes) significativamente menores, mais rápidos e mais escaláveis.

Se você quiser expressar seu "voto positivo" para um recurso, pode. use o recurso 👍 no thread inicial.

Eu definitivamente concordo que basePath deve ser um recurso integrado. Já está no roteiro e eu até escrevi um PR inicial, que você poderia ter visto lendo o tópico.

Aqui está o PR: https://github.com/zeit/next.js/pull/9872

Sinta-se à vontade para entrar em contato com [email protected] se desejar contribuir financeiramente para que esse recurso aconteça.

Todos 73 comentários

cc @jxnblk

cc @alexindigo @DullReferenceException

Adoraria receber seu feedback 👍

Depois de brincar com o código, percebi que seria muito mais fácil dividir assetPrefix em várias partes:

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

Ainda podemos manter a variável assetPrefix internamente, mas o usuário deve definir mais precisamente o que precisa.

Para a parte do ativo, é muito bom fornecer essas duas variáveis ​​juntas.
Para roteamento, etc, precisamos deles separadamente.
Ou talvez possamos até mesmo fornecê-lo juntos em um arquivo de configuração e então dividi-lo na base de código next.js. Neste caso, assetPrefix não é o nome certo, infelizmente.

Como um efeito colateral, isso também leva a menos alterações no código.
É bastante óbvio se você comparar esses dois PRs:
https://github.com/panter/next.js/pull/2 (dividir)
https://github.com/panter/next.js/pull/1 (passar em ambos)

Na minha opinião, eles devem ser separados, a razão para isso é que não é quebrável e mais flexível manter assetPrefix e ter basePath separadamente.

Então assetPrefix o nome certo? Ambas as variáveis ​​são na verdade um prefixo, certo?

assetPrefix é para ativos, por exemplo: os pacotes de páginas. basePath será para o roteador.

A maneira como deve funcionar é:

  • se assetPrefix for definido, use assetPrefix para carregar os pacotes, não toque no roteador (comportamento atual)
  • se assetPrefix e basePath forem fornecidos, use assetPrefix para carregar pacotes, adicione basePath ao roteador
  • se assetPrefix não estiver definido e basePath estiver, use basePath para carregar pacotes e adicione basePath ao roteador
  • se nem assetPrefix nem basePath são definidos, não fazemos nada diferente (comportamento atual quando assetPrefix não é fornecido)

cc @alexindigo @DullReferenceException @ 3rd-Eden

Você poderia dar um feedback sobre a proposta acima: https://github.com/zeit/next.js/issues/4998#issuecomment -414978297

@tomaswitek Não tenho certeza do que exatamente não funcionou para você com o assetPrefix atual, este é o prefixo de ativo que estamos usando na produção: "assetPrefix":"https://static.trulia-cdn.com/javascript" e funciona conforme o esperado.

E, em geral, estamos usando várias zonas (nós as chamamos de ilhas) no mesmo domínio e "basePathing" cada ilha nunca veio à nossa mente, pois isso complicaria a interoperabilidade entre as ilhas. Deixe-me explicar um pouco mais:

Portanto, temos duas ilhas A e B , e a ideia principal com elas é a transparência para os usuários que navegam de ilha em ilha como parte de sua experiência de um site. Portanto, deve haver ligações entre as ilhas. Depois, há preocupação com a implantação versus preocupação com o aplicativo.

  1. Preocupação com a implantação x preocupação com o aplicativo - o aplicativo não tem ideia de onde poderia ser implantado, apenas sabe como lidar com as solicitações HTTP recebidas - ele definiu as rotas às quais pode responder.
    Quando ele é implantado em algum lugar - podem ser domínios diferentes, portas diferentes e, sim, teoricamente, pode ser basePath diferente, que será transparente para o aplicativo via proxy ou outros meios.

  2. Ligações cruzadas entre as ilhas - para manter o espírito das ilhas como entidades implantáveis ​​separadas, não deve haver nenhum vazamento de conhecimento interno de implementação entre as diferentes ilhas.
    Portanto, a melhor maneira de as ilhas referenciarem as páginas umas das outras é exportar as rotas disponíveis para consumo de outra (s) ilha (s) (_e no mundo seguinte, parece que os componentes de <IslandALink> seriam os caminho_).
    Até agora, é tudo simples - todas as ilhas assumem que compartilham o mesmo domínio e têm seu conjunto de caminhos absolutos ( /path1 , path2 , etc). Dessa forma, a segunda ilha importa essa lista de caminhos e depende dela para ser estável. Ao mesmo tempo, é um requisito mínimo para cada ilha manter seus caminhos compatíveis com versões anteriores (o que é bom na web de qualquer maneira) :)

Quando adicionamos basePath específico de implantação, aumentamos automaticamente a complexidade de todo o sistema - cada ilha deve saber (e talvez ditar) seu próprio basePath de implantação? Então, como isso é diferente da maneira como as coisas funcionam atualmente? Ou a ilha A deve ser agnóstica quanto ao seu caminho de implantação? Então como a ilha B encontrará implantada a ilha A, já que ela só sabe o que a ilha A conhece sobre si mesma? Ou você teria que fornecer basePath para todas as ilhas implantadas para todas as outras ilhas? E com uma maneira moderna de implantar coisas, isso significa redistribuir todas as ilhas quando você precisar adicionar uma nova.

Ou como você imaginou essa parte da história?

Obrigado.

^ foi escrito antes do café da manhã, então, por favor, deixe-me saber se você precisar de uma explicação mais coerente para qualquer parte dele. :)

Em primeiro lugar, agradeço a vocês por analisarem meu problema.

@timneutkens Sim assetPrefix tem prioridade sobre basePath , isso é exatamente o que discutimos no início. Depois de ver quantos arquivos eu tinha que alterar, pensei que a segunda maneira seria mais limpa. Mas vou voltar para a primeira solução. Vamos mantê-lo totalmente separado, sem nenhum problema. Eu estava pensando alto.

@alexindigo Thx pela sua resposta detalhada. Deixe-me tentar responder às suas perguntas 😏

Não tenho certeza do que exatamente não funcionou para você com o assetPrefix atual

Tenho dois problemas aqui:

  1. Não consigo trabalhar com vários domínios nem subdomínios no projeto atual. (Restrições de domínio e nenhum certificado SSL curinga)
  2. A implementação atual de assetPrefix em um único domínio requer mais ajustes no roteamento de proxy, arquivos estáticos, etc. Poderíamos reduzir esses ajustes introduzindo basePath . Isso não vai travar nada e não vai aumentar a complexidade porque você não precisa fornecer basePath como @timneutkens já mencionado.

aplicativo não tem ideia de onde poderia ser implantado

Temos o mesmo objetivo aqui, é claro! Estamos definindo assetPrefixes dinamicamente na solução atual que temos. Ele é fornecido por meio de cabeçalhos de solicitação por proxy.

Então, como isso é diferente da maneira como as coisas funcionam atualmente?

O roteador estará ciente do contextPath e reduzirá a quantidade de código personalizado.

cada ilha deve saber (e talvez ditar) sua própria implantação basePath? Ou a ilha A deve ser agnóstica quanto ao seu caminho de implantação?

Não tem que ser. O desenvolvedor deve ter liberdade aqui. Deve ser possível fornecer basePath dinamicamente da mesma forma que assetPrefix.

Então como a ilha B encontrará implantada a ilha A, já que ela só sabe o que a ilha A conhece sobre si mesma? Ou você teria que fornecer basePath para todas as ilhas implantadas para todas as outras ilhas? E com uma maneira moderna de implantar coisas, isso significa redistribuir todas as ilhas quando você precisar adicionar uma nova.

Talvez você também possa adicionar o basePath na exportação de rotas. Eu não sei. Não estou dizendo que a variável basePath é importante para todos os casos de uso. Parece que não é a melhor solução para você. Mas tudo bem. O fato é que você ainda pode usar apenas assetPrefix e nada mudará para suas ilhas. Parece que você tem seu próprio roteamento. As ligações cruzadas entre as zonas nem mesmo são importantes para o nosso projeto, nossas zonas são realmente independentes e isoladas umas das outras.

E com uma maneira moderna de implantar coisas, isso significa redistribuir todas as ilhas quando você precisar adicionar uma nova.

Não vejo razão para isso. Posso até imaginar que algumas zonas têm basesPaths e outras não. E talvez alguns aplicativos usem a configuração do basePath mesmo sem a configuração de várias zonas.

@alexindigo, você poderia nos fornecer dois urls de ilha reais, que são renderizados por next.js para que eu pudesse vê-los em ação? Tentei encontrar um, mas não consegui encontrar uma página em seu domínio com _next solicitações 😄
Todas as suas ilhas têm a mesma configuração?
"assetPrefix":"https://static.trulia-cdn.com/javascript"

@tomaswitek

Não consigo trabalhar com vários domínios nem subdomínios no projeto atual. (Restrições de domínio e nenhum certificado SSL curinga)

Ah, então você não usa o CDN no sentido clássico, mas confia nos ativos sendo buscados diretamente em cada aplicativo? Eu vejo.

A implementação atual do assetPrefix em um único domínio requer mais ajustes no roteamento de proxy, arquivos estáticos, etc. Poderíamos reduzir esses ajustes introduzindo o basePath. Isso não vai travar nada e não vai aumentar a complexidade porque você não precisa fornecer o basePath como @timneutkens já mencionado.

Aliás, não era "não, não adicione esse recurso" :) Foi mais como - "Provavelmente podemos pensar sobre essa abordagem de forma mais holística" :)

Não tem que ser. O desenvolvedor deve ter liberdade aqui. Deve ser possível fornecer basePath dinamicamente da mesma forma que assetPrefix.

Sim. No entanto, só funciona quando não existe ligação entre as ilhas. E parece que este é o seu caso de uso. Ao mesmo tempo, estou tendo dificuldade em entender o que os torna ilhas em vez de apenas um monte de aplicativos autônomos, se eles são 100% independentes? :)

Talvez você também possa adicionar o basePath na exportação de rotas.

Não sei como isso poderia ser feito (facilmente), uma vez que a exportação de rotas acontece no momento da construção, e o basePath sendo definido no momento da implantação, e pode haver mais de uma implantação do mesmo artefato de código (estágio, pré-produção, produção, env de teste, etc).


Todas as suas ilhas têm a mesma configuração?
"assetPrefix": " https://static.trulia-cdn.com/javascript "

Sim, todas as ilhas compartilham seus ativos, já que a seguir faz o hashing de conteúdo, não é apenas um problema, mas na verdade muito benéfico. (Extraímos ativos construídos de cada artefato e publicamos no CDN no momento da implantação).

E dessa forma, temos apenas solicitações "html regulares" para nossos servidores de aplicativos, é por isso que não vejo nenhum caminho "_next" em trulia.com

Quanto aos exemplos das ilhas:

Nossa nova ilha - página de bairros - https://www.trulia.com/n/ca/san-francisco/pacific-heights/81571 (e você pode encontrar mais deles aqui: http: //www.trulia. com / bairros)
Esta ilha é responsável por todos os /n/* caminhos.

E outra ilha é nossa página de login - https://login.trulia.com/login - parece um domínio diferente, mas realmente não é, parece assim por diferentes motivos, mas tecnicamente é a mesma implantação. :)
E esta ilha lida com urls como /login , /signup .

Deixe-me saber se você tem mais perguntas.

@alexindigo muito obrigado por seus exemplos.
Tenho algumas perguntas depois de analisar os exemplos 😄

Você ainda faz a renderização do servidor para cada ilha, mas tenta extrair o máximo de ativos possíveis em um CDN comum, certo?

Por favor, você pode descrever um pouco mais o que exatamente acontece quando https://www.trulia.com/n/ca/san-francisco/pacific-heights/81571 é chamado? Seu proxy sabe que /n significa visão geral da vizinhança e o encaminha para a ilha certa? Isso afeta de alguma forma o pedido antes de chegar à ilha?

Você usa o roteamento integrado próximo a uma ilha ou tem uma solução personalizada?
Queria verificar o roteamento dentro de sua ilha. Infelizmente Neighborhood overview tem mais ou menos navegação modal sem alterar o url. No Login, parece haver uma solução totalmente personalizada.

Espero responder a todas as suas perguntas neste comentário 😏

Aliás, não era "não, não adicione esse recurso" :) Foi mais como - "Provavelmente podemos pensar sobre essa abordagem de forma mais holística" :)

Claro, seria ótimo encontrar uma solução onde eu não precise tocar em next.js 😏

Sim. No entanto, só funciona quando não existe ligação entre as ilhas. E parece que este é o seu caso de uso. Ao mesmo tempo, estou tendo dificuldade em entender o que os torna ilhas em vez de apenas um monte de aplicativos autônomos, se eles são 100% independentes? :)

Nunca escrevi nem disse que procuro uma solução "ilha". Acabei de bater um papo com next.js não oferece suporte a caminhos de base. E depois de pesquisar um pouco no Google, percebi que não sou o único procurando por ele. Então achei que poderia contribuir um pouco. Depois disso, Tim enviou um ping para você para me dar um feedback e estou muito grato por seus comentários.

Não sei como isso poderia ser feito (facilmente), uma vez que a exportação de rotas acontece no momento da construção, e o basePath sendo definido no momento da implantação, e pode haver mais de uma implantação do mesmo artefato de código (estágio, pré-produção, produção, env de teste, etc).

Bem, se você deseja exportar rotas em tempo de construção e torná-las disponíveis para outras ilhas, então a única maneira direta é provavelmente codificar o basePath na configuração. Eu entendi seu ponto. Por outro lado, isso é realmente um problema? Você ainda pode implantar o aplicativo em diferentes domínios e portas e pode usar o mesmo basePath para cada env.

Bom dia @tomaswitek :)

Minha experiência com a funcionalidade "basePath" engana muito em sua complexidade, e geralmente é melhor implementar esse tipo de coisa sem me precipitar com um problema específico,
mas olhando de vários ângulos. Semelhante a como você abordaria a fusão profunda - descreva vários casos de uso e veja como (e se) todos eles se enquadram no mesmo guarda-chuva. Já que ter recursos incompatíveis entre (mesmo as principais) versões do framework é muito chato :)

Você ainda pode implantar o aplicativo em diferentes domínios e portas e pode usar o mesmo basePath para cada env.

Parece que você concordaria com a solução em que "basePath" faz parte do seu código de roteamento, algo que você mencionou no início - como uma subpasta dentro do diretório pages (aliás, essa abordagem seria um sinal para os desenvolvedores escolhidos basePath muito bem). Mas a única coisa que o impediu é que o próximo caminho interno para os ativos _next não é configurável.

E isso soa como um problema mais estreito que podemos resolver com menos efeitos colaterais de longo prazo.

E pode nos levar ainda mais longe, como se pudéssemos configurar assetPath por ativo (por exemplo, com o mapa next.config de algum tipo) - isso nos permitirá ter ativos compartilhados entre os aplicativos, o que melhorará o desempenho, e outras coisas.

E há relações públicas abertas para esse recurso. ;) / cc @timneutkens parece que é hora de voltar para aquele cachorro. :)

Se você não vai adicionar isso em breve, poderíamos obter um server.js baseado em express de exemplo adicionado ao readme que faça isso e funcione? Tentei alguns que estão circulando nesses problemas, mas não consegui fazê-los funcionar. Obrigado.

Olá @ccarse , tenho um fork em funcionamento que já usamos na produção: https://github.com/panter/next.js/pull/2
Também estou pronto para investir tempo para abrir um PR para esse recurso.
@timneutkens @alexindigo existe outra maneira de resolver este problema?
Se não precisarmos de uma configuração basePath , você pode nos dar um exemplo mínimo usando assetPath ?

Minha empresa também está enfrentando isso.

Aos poucos, estamos assumindo o controle de um aplicativo legado, seção por seção, e substituindo-o por Next.js.

Como um exemplo simplificado:

| URL | App |
| --- | --- |
| example.com | legado |
| example.com/shop | próximo |
| example.com/search | legado |
| example.com/members | próximo |

Isso significa que queremos que tudo seja prefixado em cada aplicativo Next.js ... Páginas, rotas, ativos, etc.

É importante notar também que não estamos usando agora, por isso não podemos tirar proveito de now.json roteamento. Temos nosso próprio balanceador de carga na frente de todo o domínio e, em seguida, roteamos o tráfego com base no subcaminho.

Também estamos usando um servidor personalizado (hapi), então seria bom se pudéssemos aproveitar o que quer que seja criado aqui dentro de um servidor personalizado também.

Talvez haja alguma combinação de configurações now.config.json ou algum uso de micro-proxy que possamos usar para realizar essa mesma coisa, mas ainda não descobrimos a combinação certa.

Estamos enfrentando, eu acho, o mesmo problema com vários aplicativos Next.js exportados estaticamente e hospedados no Now v2.

| URL | App |
| - | - |
| example.com | próximo |
| example.com/dashboard | próximo |

Como esperado, o aplicativo root funciona bem. As coisas dão errado no segundo, no entanto. No momento, estamos empacotando next/link que, combinado com assetPrefix , resolve a maior parte do problema:

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

No entanto, isso quebra prefetch porque tenta procurar .js arquivos no URL errado:

Nossa solução alternativa atual é desativar prefetch , o que não é o ideal.

Qual é o status disso?

Também estou procurando uma atualização sobre isso, por favor.

@timneutkens Estou pronto para investir tempo para abrir um PR, se a comunidade estiver interessada. Já estamos usando uma solução (https://github.com/panter/next.js/pull/1) em produção e estamos muito felizes com ela.

Também precisamos de uma solução para isso

Em breve apresentaremos uma nova API que tornará esta proposta obsoleta.

Também impactado por isso. Precisa executar o próximo projeto em um caminho de subdiretório. Ansioso para o recurso oficial. Existe um HEC?

API

Então, como vai? : D

Não envie spam para o tópico e use o recurso 👍 do GitHub no problema em si.

@timneutkens Você pode fornecer mais informações? Qual é a API que tornará isso obsoleto? O que você considera "em breve"? Obrigado.

Isso pode não estar exatamente relacionado a zonas múltiplas, mas pode ajudar ...

Resolvi algo semelhante a isso criando um servidor personalizado e usando middleware de proxy

por exemplo: @Zertz
Observação: você ainda precisa resolver o problema do link - novamente, resolvi isso criando um componente de link e passando o prefixo para o aplicativo via configuração e, se houver um prefixo, use-o ou não use nada, o mesmo para imagens estáticas.

const proxy = require('http-proxy-middleware');

app.setAssetPrefix('/dashboard');

  // Express custom server
  // Proxy so it works with prefix and without...
  // So if asset prefix is set then it still works
  const server = express();
  server.use(
    proxy('/dashboard', {
      target: 'http://localhost:3000', 
      changeOrigin: true,
      pathRewrite: {
        [`^/dashboard`]: '',
      },
    }),
  );

A proposta que mencionei é # 7329

A proposta que mencionei é # 7329

@timneutkens
Você pode fornecer mais detalhes sobre como o gancho proposto resolverá nossos problemas de caminho de base?
E os redirecionamentos de roteador como Router.push('/about') também serão substituídos por um gancho?

Obrigado pelo seu tempo 😏

A API do roteador também mudaria, pois precisaria de um componente para o link. Nesse ponto, você pode usar caminhos relativos para o próprio url.

Alguma atualização sobre quando podemos obter uma solução ou pelo menos uma solução alternativa para isso?

Use 👍 na edição inicial em vez de postar qualquer atualização.

@ MMT-LD Sua solução meio que funciona para mim, mas agora a cada clique de link ou evento de envio de roteador a página é recarregada ☹️

Tentei a solução de @Zertz e funcionou perfeitamente!
Também poderia corrigir prefetch problema copiando arquivos de saída para os caminhos pré-buscados.
https://github.com/fand/MDMT/blob/master/scripts/copy-preload.js

... é um truque sujo, mas está funcionando de qualquer maneira🤪

@nicholasbraun

Agora, a cada clique de link ou evento de envio de roteador, a página é recarregada ☹️

Tive esse problema, mas resolvi usar o parâmetro 'as' no link, então o link aponta para o arquivo interno, mas o 'as' é relativo ao caminho
por exemplo:
<Link href={"/${item.link}"} as={"./${item.link}"}>

@nicholasbraun

Sua solução meio que funciona para mim, mas agora em cada clique de link ou evento de envio de roteador, a página é recarregada ☹️

Isso é o que eu quis dizer. Isso é de memória ... mas tenho certeza de que você não pode obter o que precisa a partir de baixo.

// WithConfig component
import getConfig from 'next/config';

const { publicRuntimeConfig } = getConfig();

const WithConfig = ({ children }) =>
  children && children({ config: publicRuntimeConfig });

export default WithConfig;
// Extended Link component

 import React from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';
import { WithConfig } from '../WithConfig';
/* 
    <Link> component has two main props:
    href: the path inside pages directory + query string. e.g. /page/querystring?id=1
    as: the path that will be rendered in the browser URL bar. e.g. /page/querystring/1

*/

const NextLink = ({
  browserHref,
  pagesHref,
  whatever,
}) => {
  return (
    <WithConfig>
      {({ config: { pathPrefix } = {} }) => (
        <Link
          as={pathPrefix ? `${pathPrefix}${browserHref}` : browserHref}
          href={pagesHref}
          passHref
        >
          <a>{whatever}</a> // this bit is up to you - children or whatever
        </Link>
      )}
    </WithConfig>
  );
};

NextLink.propTypes = {
  browserHref: PropTypes.string.isRequired,
  pagesHref: PropTypes.string,
};

NextLink.defaultProps = {
  pagesHref: undefined,
};

export default NextLink;

Uso:

import NextLink from '../NextLink'

<NextLink browserHref={`/page/1`} pagesHref={`/page?querystring=1`} whatever='I'm the link' />

Boa sorte: smiley:

Como o useLink RFC foi recusado (# 7329) e ter basePath suporte nos ajudaria muito, o projeto Next.js está feliz em aceitar PRs implementando-o? Estou disposto a fazer isso.

Olhando para esta implementação de @tomaswitek , ela parece estar indo na direção certa, o mais importante é alertar o roteador sobre basePath . Existem outras coisas não óbvias que tornariam o suporte de basePath difícil?

No geral, acho que o design é claro, apenas uma única variável de configuração:

module.exports = {
  basePath: '/demo'
}

As interações com assetPrefix estão bem definidas aqui: https://github.com/zeit/next.js/issues/4998#issuecomment -414978297.


ATUALIZAÇÃO : Eu também estava pensando se seria possível implementar isso criando um roteador personalizado e de alguma forma trocando o padrão, mas não parece ser possível, Next.js codifica seu roteador, veja, por exemplo, aqui . Também estou cético de que "apenas" substituir o roteador seria suficiente; o recurso provavelmente precisa ser suportado por Next.js como um todo.

Este problema existe desde 2017, existe alguma solução alternativa? Ou uma resposta oficial à nossa solicitação de basePath?

Então, depois de tentar fazer isso funcionar com a combinação de assetPrefix e um componente <Link> , conforme sugerido em, por exemplo, https://github.com/zeit/next.js/issues/4998#issuecomment -464345554 ou https://github.com/zeit/next.js/issues/4998#issuecomment -521189412, não acredito que possa ser feito, infelizmente.

Definir assetPrefix foi relativamente simples, algo assim em next.config.js :

const assetPrefix = process.env.DEPLOYMENT_BUILD ? '/subdir' : '';

module.exports = {
  assetPrefix,
  env: {
    ASSET_PREFIX: assetPrefix,
  },
}

A próxima etapa é um componente Link . A primeira idéia, dada por exemplo em https://github.com/zeit/next.js/issues/4998#issuecomment -464345554, é prefixar href assim (simplificado):

export default ({ children, href, ...rest }) => (
  <Link href={`${process.env.ASSET_PREFIX}${href}`} {...rest}>
    {children}
  </Link>
);

Conforme relatado por outros nesta discussão, isso quebra prefetching como os pedidos são subitamente para / subdir /_next/static/.../pages/ subdir /example.js - o outro "subdir" não deveria estar lá. Mas com nosso componente Link , estamos definindo href como /subdir/example , então não é de se admirar que Next.js esteja solicitando um pacote da página pages/subdir/example.js .

Então, OK, a pré-busca problemática não parece o fim do mundo (embora a UX seja muito feia), mas em nosso aplicativo, as coisas pioram à medida que usamos o roteamento dinâmico do Next.js 9. Para isso, precisamos definir as corretamente para que a evolução do componente Link assim:

export default ({ children, href, as, ...rest }) => (
  <Link 
    href={`${process.env.ASSET_PREFIX}${href}`}
    as={`${process.env.ASSET_PREFIX}${as}`}
    {...rest}
  >
    {children}
  </Link>
);

O uso é:

<CustomLink href='/post/[id]' as='/post/1'>...</CustomLink>

que é convertido em:

<Link href='/subdir/post/[id]' as='/subdir/post/1'>...</Link>

e isso não funcionou para mim quando implantado no Now - tentar navegar para https://deployment-id.now.sh/subdir/post/1 levou a 404. Não tenho certeza do motivo, talvez seja também um problema com o construtor @now/next ( ATUALIZAÇÃO : é por causa de https://github.com/zeit/next.js/pull/8426#issuecomment-522801831) mas, no final das contas, estamos confundindo o roteador Next.js quando pedimos /subdir/post/[id] componente quando esse arquivo não existe no disco.

Há outro exemplo neste tópico, https://github.com/zeit/next.js/issues/4998#issuecomment -521189412, que prefixa apenas como , não href, assim (simplificado):

export default ({ children, href, as, ...rest }) => (
  <Link href={href} as={`${process.env.ASSET_PREFIX}${as}`} {...rest}>
    {children}
  </Link>
);

mas isso gerará este erro no navegador:

Seu <Link> as é incompatível com o href . Isso é inválido.

É um problema relatado em https://github.com/zeit/next.js/issues/7488.

Depois de tudo isso, não acho que haja uma solução até que algo como basePath seja suportado, o que terei prazer em ajudar.

@borekb Também estou pronto para ajudar, como mencionei algumas vezes antes. Todas as soluções alternativas que vi até agora resolvem apenas parte do problema. No momento, estamos usando um fork do next.js na produção, que implementa o basePath.

Em breve apresentaremos uma nova API que tornará esta proposta obsoleta.

@tim Ainda é o caso ou a nova proposta de API foi encerrada? https://github.com/zeit/next.js/issues/7329

Btw. amanhã fará exatamente um ano abri esta edição 🎉

Uma ideia relativamente selvagem é ter páginas em algo como src/pages e, em seguida, vinculá-las simbolicamente ao local apropriado. Por exemplo:

  • Para implantar em myapp.example.com , eu vincularia src/pages a pages
  • Para implantar em example.com/myapp , eu vincularia src/pages a pages/myapp

Em combinação com o componente <Link> e assetPrefix , _poderia_ funcionar, mas não tenho coragem de experimentá-lo 😄.

Alguma atualização com isso?

Algum progresso no suporte de basePath ? :)

@nicholasbraun

Agora, em cada clique de link ou evento de envio de roteador, a página recarrega frowning_face

Tive esse problema, mas resolvi usar o parâmetro 'as' no link, então o link aponta para o arquivo interno, mas o 'as' é relativo ao caminho
por exemplo:
<Link href={"/${item.link}"} as={"./${item.link}"}>

Você salvou meu dia! :)))

estou fazendo o mesmo com Router.push(`/route`, `${process.env.BASE_PATH}route`);

@nicholasbraun

Agora, a cada clique de link ou evento de envio de roteador, a página é recarregada ☹️

Tive esse problema, mas resolvi usar o parâmetro 'as' no link, então o link aponta para o arquivo interno, mas o 'as' é relativo ao caminho
por exemplo:
<Link href={"/${item.link}"} as={"./${item.link}"}>

Esta solução não funciona com o próximo roteamento baseado em 9 arquivos. /route/[id] , ${process.env.BASE_PATH}/route${id} gera este erro

Este comentário explica o problema muito bem.

Embora eu tenha visto algumas pessoas discutindo como as soluções aqui quebram a pré-busca. Para nós, há outra questão mais importante.

Com next9, usar um assetPrefix em seu href faz next _sempre_ executar uma rota de servidor. Eu criei um repositório de reprodução nesta edição que demonstra o que está acontecendo.

Isso basicamente quebra o cache do cliente Apollo, pois ele é recriado em todas as rotas.

Acho que a implementação está comparando a página subjacente href sem um assetPrefix com as próximas rotas href (que inclui um assetPrefix) - resultando em uma rota profunda.

por exemplo, se você estiver em href /prefix/page (a página subjacente é apenas /page ) e sua próxima rota href é /prefix/page/[id] (porque sem o prefixo será 404) esta é uma rota completamente diferente e uma rota rasa não é possível.

Procurando soluções imediatas com rotas expressas

Quando usar componente com os href props que é basePath, a pré-busca não está funcionando.
PLZ suporta basePath e pré-busca, será incrível

Eu poderia realmente usar isso. Estou executando vários aplicativos de uma única fonte de servidor e cada um separado em seu próprio web/appX/{next project files} . Seria ótimo ter mais controle sobre o basePath. Eu descobri uma solução alternativa por enquanto, mas não é muito bonito.

a exportação estática também precisa de basePath 😊

parece sucesso no trabalho 👏

{
  experimental:{
    basePath: '/some/dir',
  }
}

Esta é uma limitação muito ruim para nós, infelizmente :(

Temos todos os aplicativos atrás de um proxy reverso, portanto, os caminhos precisam ser prefixados (no exemplo abaixo, é o prefixo de /auction-results )

Já usamos o prefixo assetPrefix - e isso permite que os aplicativos sejam executados corretamente para solicitações do servidor.
Por exemplo: mydomain.com/auction-results/ funciona bem usando algum roteamento expresso como:

router.get(`/${appPrefix}/`, (req, res) => {
  nextApp.render(req, res, '/national', req.params);
});

Mas quando tentamos fazer a navegação do lado do cliente via next/link , por exemplo:

Onde /auction-results é o prefixo do aplicativo e /national é a página em ~pages/national

<Link href="/national" as="/auction-results/">
  <a>Goto National Page</a>
</Link>

Isso não faz nada (clique fantasma)

Ter links de atualização de página inteira não é o ideal.

Se houver alguma maneira de eu poder ajudar com isso, eu adoraria

Qualquer atualização sobre isso ... no ano passado, nessa época, me deparei com esse problema. Agora, um ano depois, estou trabalhando em um novo aplicativo e tenho que fazer as mesmas soluções alternativas que fiz no ano passado ... um pouco alarmante para uma reação "pronta para produção". Os caminhos de base devem ser uma característica comum.

Qualquer atualização sobre isso ... no ano passado, nessa época, me deparei com esse problema. Agora, um ano depois, estou trabalhando em um novo aplicativo e tenho que fazer as mesmas soluções alternativas que fiz no ano passado ... um pouco alarmante para uma reação "pronta para produção". Os caminhos de base devem ser uma característica comum.

Não tenho certeza do que você espera ao postar isso.

Next.js está sendo trabalhado em tempo integral pela minha equipe (5 pessoas), e estamos trabalhando em muitos recursos ao mesmo tempo. No ano passado, trabalhamos nisso:

Tornando eficazmente os aplicativos Next.js (novos e existentes) significativamente menores, mais rápidos e mais escaláveis.

Se você quiser expressar seu "voto positivo" para um recurso, pode. use o recurso 👍 no thread inicial.

Eu definitivamente concordo que basePath deve ser um recurso integrado. Já está no roteiro e eu até escrevi um PR inicial, que você poderia ter visto lendo o tópico.

Aqui está o PR: https://github.com/zeit/next.js/pull/9872

Sinta-se à vontade para entrar em contato com [email protected] se desejar contribuir financeiramente para que esse recurso aconteça.

Qual é o status disso? estamos realmente dependendo disso: /

O suporte do

cf. # 9872

@martpie eu já vi, mas para. meu caso basePath não é apenas um, pode ser vários basePath, uma vez que servimos nosso aplicativo por meio de diferentes "URLs" e configurar basePath durante o tempo de compilação não é uma opção (mesmo que tenha para suportar uma série de caminhos em vez de uma única string)

@timneutkens Obrigado pela atualização. Você faria a gentileza de dar outra atualização. Isso é para nós uma característica fundamental e precisamos saber ...

  1. Será esta apenas uma empresa (sua referência para entrar em contato com o departamento de vendas da empresa causou alguma irritação)?

  2. Parece que está no roadmap, de acordo com o PR não será removido novamente; você pode dar alguma indicação se é seguro construir em torno deste recurso agora sem nenhuma surpresa nos próximos meses, como uma versão de código aberto inválida e outra com suporte total depois de negociarmos semanas com alguns vendedores aleatórios sobre preços arbitrários?

Eu entendo que vocês trabalham em muitos recursos e todos têm suas prioridades, mas configurações ainda menores precisam de proxy. Em seguida, execute várias instâncias e dê a ele basePath por serviço dedicado. Antes de começarmos a construir vários serviços no Next, precisamos saber quão provável e em breve esse recurso estará disponível como código aberto completo. Caso contrário, seria muito arriscado investir mais tempo no Next.

Agradecemos sua compreensão e aguardamos seus comentários.

FWIW, agora estou funcionando e para outros que estão dirigindo por:

Coloque isso em seu next.config.js :

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

Então, eu precisei reiniciar o servidor e configurar o middleware do meu servidor da web corretamente:

Pego todas as solicitações por meio de um caminho personalizado, por exemplo. app.use('/custom', (req, res...) => { ... e então (o que era importante) eu preciso proxy para a URL do sistema onde o Next está rodando (então o endereço interno de sua orquestração de contêiner e novamente com o respectivo caminho se você usar http-proxy => por exemplo. ... target: 'http://next:3000/custom ), não apenas o host sem o caminho personalizado. Se você usar http-proxy-middleware você não precisa disso.

Parece muito bom, espero que este recurso não precise de nenhuma licença EE. Se sua equipe precisar de ajuda para amadurecer esse recurso, por favor, nos avise, talvez possamos ajudar!

Edit: Tentei isso também no modo de produção do Next e parece funcionar bem.

@timneutkens Obrigado pela atualização. Você faria a gentileza de dar outra atualização. Isso é para nós uma característica fundamental e precisamos saber ...

  1. Será esta apenas uma empresa (sua referência para entrar em contato com o departamento de vendas da empresa causou alguma irritação)?
  2. Parece que está no roadmap, de acordo com o PR não será removido novamente; você pode dar alguma indicação se é seguro construir em torno deste recurso agora sem nenhuma surpresa nos próximos meses, como uma versão de código aberto inválida e outra com suporte total depois de negociarmos semanas com alguns vendedores aleatórios sobre preços arbitrários?

Eu entendo que vocês trabalham em muitos recursos e todos têm suas prioridades, mas configurações ainda menores precisam de proxy. Em seguida, execute várias instâncias e dê a ele basePath por serviço dedicado. Antes de começarmos a construir vários serviços no Next, precisamos saber quão provável e em breve esse recurso estará disponível como código aberto completo. Caso contrário, seria muito arriscado investir mais tempo no Next.

Agradecemos sua compreensão e aguardamos seus comentários.

@ pe-s Acho que você está entendendo mal a minha postagem.

Não existe uma "versão Enterprise Next.js" no momento. Eu estava me referindo às inúmeras ocasiões em que empresas externas procuraram pagar por consultoria para desenvolver recursos como este em um período mais curto. Por exemplo, o suporte de zonas foi criado em colaboração com a Trulia.

Este recurso ainda está sendo trabalhado e está no roteiro. Todos os recursos que estão sendo trabalhados são de código aberto, como eu disse, não há versão corporativa do Next.js. Temos várias prioridades de trabalho de alto impacto no roteiro, embora seja por isso que me referi a entrar em contato com [email protected] se você precisar desse recurso o mais rápido possível / para discutir o suporte corporativo para Next.js.

@timneutkens tx pela sua resposta rápida e excelente! Então, podemos ir all in :)
Continue com o ótimo trabalho!

O suporte do Basepath está disponível em next@canary agora, não é mais experimental. Estará no canal estável em breve.

Estou muito atrasado para isso, mas você considerou usar HTML real

O suporte do Basepath está disponível em next@canary agora, não é mais experimental. Estará no canal estável em breve.

@timneutkens , obrigado por esta adição. Você sabe quando o suporte basePath não experimental será lançado oficialmente?

Além disso, quando eu defino o basePath, os ativos (localizados na pasta pública) são servidos no url apropriado, conforme o esperado. Mas, quando faço referência a eles em meu código, tenho que adicionar o caminho base ao src manualmente, porque caso contrário, eles ainda serão referenciados a partir do caminho normal. Este é o uso esperado de basePath? Eu também tentei usar o assetPrefix, mas não teve nenhum efeito no meu código que eu pudesse dizer.

Exemplo :

  1. usando next v9.4.5-canary.24
  2. basePath definido como /alerts em next.config.js:
const basePath = '/alerts';
module.exports = {
  basePath: basePath,
  env: {
    BASE_PATH: basePath,
  },
};
  1. ativo localizado em public/images/example.png
  2. exemplo de uso de ativo no componente de reação:
const ExampleImage = () => (
  <img src={`${process.env.BASE_PATH}/images/example.png`} />
);

Em meus testes, não estou atualizando os URLs dos ativos.

Instalei o canário mais recente:
npm install [email protected]

next.config.js

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

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

Todas as páginas e links carregam corretamente:
http: // localhost : 3000 / example / posts / pré-renderização
http: // localhost : 3000 / example / posts / ssg-ssr
http: // localhost : 3000 / example / posts / pré-renderização

Mas imagens, favicons etc. não são mapeados:
http: // localhost : 3000 / favicon.ico 404
http: // localhost : 3000 / images / profile.jpg 404

Alguém testou isso? Também tentei usar o assetPrefix, mas também não funcionou.

Além disso, estou confuso, por que não usar a funcionalidade do navegador embutido para isso?
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

Obrigado por analisar isso também, @kmturley . Fico feliz em saber que não sou só eu.
@timneutkens , devemos reabrir este problema / criar um novo problema para este bug?

Você tem que prefixar as imagens manualmente. Você pode obter o basePath usando

const {basePath} = useRouter()

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

Next.js usará automaticamente seu prefixo nos scripts que carrega, mas isso não tem nenhum efeito na pasta pública;

Agora, eu percebi que existem várias maneiras de vincular arquivos em / public. por exemplo, <img/> <link/> ...
É por isso que temos que especificar manualmente o basePath para cada um?

Se houvesse um componente como o abaixo disponível, acho que economizaria tempo e reduziria as confusões de muita gente?

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

Eu realmente não acho que isso seja apropriado, mas é isso que eu quis dizer.

// 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

Para aqueles que estão tentando usar const {basePath} = useRouter() que é um Gancho, para trabalhar com Classes e Componentes e obtendo este erro:

Aviso de chamada de gancho inválido

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

Você pode fazê-lo funcionar usando:

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)

Se você quiser usar basePath com markdown, parece que você precisa fazer um localizar e substituir na 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>
)

Você tem que prefixar as imagens manualmente. Você pode obter o basePath usando

const {basePath} = useRouter()

Esta solução não leva em consideração imagens importadas em um arquivo css ou scss. Você tem uma solução sobre como definir o caminho base ao importar um ativo de um arquivo css ou scss?
Com esta solução, teremos que garantir que todas as imagens sejam importadas por meio de uma tag img, estilo inline ou na tag de estilo. Não é o ideal, porque dividirá seus estilos para serem implementados em vários lugares.

@peetjvv Aqui está uma solução <CSSVariables> em _app.tsx , que injeta um elemento <style> global embutido contendo variáveis ​​CSS, que você pode usar em todas as suas folhas de estilo.

Por exemplo, na abertura de <body> variáveis ​​de construção e injeção:

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

Para obter esse basePath, uso a abordagem de @kmturley usando withRouter .
Veja como esse componente poderia ser:

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);
Esta página foi útil?
0 / 5 - 0 avaliações