Os URLs do React-router não funcionam ao atualizar ou gravar manualmente

655

Estou usando o React-router e funciona bem enquanto clico nos botões de link, mas quando atualizo minha página da Web, ele não carrega o que quero.

Por exemplo, estou dentro localhost/jobliste está tudo bem porque cheguei aqui pressionando um link. Mas se eu atualizar a página da Web, recebo:

Cannot GET /joblist

Por padrão, não funcionou assim. Inicialmente, eu tinha o meu URL como localhost/#/e localhost/#/jobliste eles funcionou perfeitamente bem. Mas eu não gosto desse tipo de URL, então, tentando apagar isso #, escrevi:

Router.run(routes, Router.HistoryLocation, function (Handler) {
 React.render(<Handler/>, document.body);
});

Este problema não ocorre com localhost/, este sempre retorna o que eu quero.

EDIT: Este aplicativo é de página única, por /joblistisso não precisa pedir nada a nenhum servidor.

EDIT2: Meu roteador inteiro.

var routes = (
    <Route name="app" path="/" handler={App}>
        <Route name="joblist" path="/joblist" handler={JobList}/>
        <DefaultRoute handler={Dashboard}/>
        <NotFoundRoute handler={NotFound}/>
    </Route>
);

Router.run(routes, Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, document.body);
});
DavidDev
fonte
a menos que você use o htaccess para carregar sua página principal de turismo e diga ao seu roteador para usar location.pathname, ele não funcionará ..
Charles John Thompson III -
Como você apagou esse #símbolo? Obrigado!
SudoPlz 06/06
4
Se você estiver hospedando seu aplicativo de reação em um bucket S3, basta definir o documento de erro como index.html. Isso garantirá que index.htmlseja atingido, não importa o quê.
Trevor Hutto 30/07
No meu caso, ele funciona muito bem no Windows, mas não no linux
Ejaz Karim
Esta é a referência que ajudou a resolver o meu problema: github.com/facebook/create-react-app/blob/master/packages/...
jimbotron

Respostas:

1096

Analisando os comentários sobre a resposta aceita e a natureza genérica dessa pergunta ('não funciona'), achei que esse seria um bom lugar para algumas explicações gerais sobre os problemas envolvidos aqui. Portanto, esta resposta é destinada a informações / elaboração de plano de fundo sobre o caso de uso específico do OP. Por favor, tenha paciência comigo.

Lado do servidor x lado do cliente

A primeira coisa importante a entender sobre isso é que agora existem 2 lugares onde a URL é interpretada, enquanto costumava haver apenas 1 nos 'velhos tempos'. No passado, quando a vida era simples, algum usuário enviava uma solicitação para http://example.com/abouto servidor, que inspecionava a parte do caminho da URL, determinava que o usuário estava solicitando a página sobre e depois a enviava de volta.

Com o roteamento do lado do cliente, que é o que o React-Router fornece, as coisas são menos simples. Inicialmente, o cliente ainda não possui nenhum código JS carregado. Portanto, a primeira solicitação sempre será para o servidor. Isso retornará uma página que contém as tags de script necessárias para carregar o React e o React Router, etc. Somente quando esses scripts foram carregados, a fase 2 é iniciada. Na fase 2, quando o usuário clica no link de navegação "Sobre nós", por exemplo, o URL é alterado localmente apenas para http://example.com/about(tornado possível pela API de histórico ), mas nenhuma solicitação ao servidor é feita. Em vez disso, o React Router faz sua parte no lado do cliente, determina qual visualização do React renderizar e a renderiza. Supondo que sua página sobre não precise fazer chamadas REST, isso já foi feito. Você fez a transição da Página inicial para Sobre nós sem que nenhuma solicitação do servidor tenha sido acionada.

Então, basicamente, quando você clica em um link, algumas execuções de Javascript que manipulam o URL na barra de endereços, sem causar uma atualização da página , o que faz com que o React Router faça uma transição de página no lado do cliente .

Mas agora considere o que acontece se você copiar e colar o URL na barra de endereços e enviá-lo por e-mail a um amigo. Seu amigo ainda não carregou seu site. Em outras palavras, ela ainda está na fase 1 . O React Router ainda não está em execução em sua máquina. Então, o navegador dela fará uma solicitação ao servidorhttp://example.com/about .

E é aí que o seu problema começa. Até agora, você poderia simplesmente colocar um HTML estático na raiz da web do seu servidor. Mas isso daria 404erros para todos os outros URLs quando solicitados ao servidor . Esses mesmos URLs funcionam bem no lado do cliente , porque o React Router está fazendo o roteamento para você, mas eles falham no lado do servidor, a menos que você faça com que o servidor os entenda.

Combinando o roteamento do servidor e do cliente

Se você deseja que o http://example.com/aboutURL funcione no servidor e no cliente, é necessário configurar rotas para ele no servidor e no cliente. Faz sentido certo?

E é aí que as suas escolhas começam. As soluções vão desde contornar completamente o problema, por meio de uma rota abrangente que retorna o HTML de inicialização, até a abordagem isomórfica completa, na qual o servidor e o cliente executam o mesmo código JS.

.

Ignorando o problema completamente: Histórico de Hash

Com o Hash History em vez do Browser History , o URL da página about seria parecido com o seguinte: http://example.com/#/about A parte após o #símbolo hash ( ) não é enviada para o servidor. Portanto, o servidor apenas vê http://example.com/e envia a página de índice conforme o esperado. O React-Router pega a #/aboutpeça e mostra a página correta.

Desvantagens :

  • URLs 'feios'
  • A renderização do servidor não é possível com essa abordagem. No que diz respeito à otimização de mecanismos de busca (SEO), seu site consiste em uma única página com quase nenhum conteúdo.

.

Catch-all

Com essa abordagem, você fazer História Navegador uso, mas apenas configurar um pega-tudo no servidor que envia /*para index.html, efetivamente dando-lhe muito a mesma situação que com a História Hash. No entanto, você possui URLs limpos e poderá aprimorar esse esquema posteriormente sem precisar invalidar todos os favoritos do usuário.

Desvantagens :

  • Mais complexo de configurar
  • Ainda não é um bom SEO

.

Híbrido

Na abordagem híbrida, você expande o cenário abrangente adicionando scripts específicos para rotas específicas. Você pode criar alguns scripts PHP simples para retornar as páginas mais importantes do seu site com o conteúdo incluído, para que o Googlebot possa pelo menos ver o que está na sua página.

Desvantagens :

  • Ainda mais complexo de configurar
  • Apenas um bom SEO para as rotas que você dá ao tratamento especial
  • Código de duplicação para renderizar conteúdo no servidor e no cliente

.

Isomórfico

E se usarmos o Nó JS como nosso servidor para que possamos executar o mesmo código JS nas duas extremidades? Agora, temos todas as nossas rotas definidas em uma única configuração do roteador de reação e não precisamos duplicar nosso código de renderização. Este é 'o Santo Graal', por assim dizer. O servidor envia exatamente a mesma marcação que terminaríamos se a transição da página tivesse acontecido no cliente. Esta solução é ideal em termos de SEO.

Desvantagens :

  • O servidor deve (pode) executar o JS. Eu experimentei o Java icw Nashorn, mas não está funcionando para mim. Na prática, significa principalmente que você deve usar um servidor baseado em Nó JS.
  • Muitas questões ambientais complicadas (usando windowno lado do servidor etc)
  • Curva de aprendizado acentuada

.

Qual devo usar?

Escolha o que você pode se safar. Pessoalmente, acho que o catch-all é simples o suficiente para configurar, então esse seria o meu mínimo. Essa configuração permite que você melhore as coisas ao longo do tempo. Se você já está usando o Node JS como sua plataforma de servidor, eu definitivamente investigaria a execução de um aplicativo isomórfico. Sim, é difícil no começo, mas quando você pega o jeito, na verdade é uma solução muito elegante para o problema.

Então, basicamente, para mim, esse seria o fator decisivo. Se meu servidor for executado no nó JS, eu ficaria isomórfica; caso contrário, eu escolheria a solução Catch-all e a expandiria (solução híbrida) à medida que o tempo avança e os requisitos de SEO exigem.

Se você quiser saber mais sobre a renderização isomórfica (também chamada de 'universal') com o React, existem alguns bons tutoriais sobre o assunto:

Além disso, para você começar, recomendo olhar alguns kits iniciais. Escolha uma que corresponda às suas escolhas para a pilha de tecnologia (lembre-se, React é apenas o V no MVC, você precisa de mais coisas para criar um aplicativo completo). Comece analisando o publicado pelo próprio Facebook:

Ou escolha um dos muitos da comunidade. Existe um site legal agora que tenta indexar todos eles:

Comecei com estes:

Atualmente, estou usando uma versão caseira da renderização universal inspirada nos dois kits iniciais acima, mas eles estão desatualizados agora.

Boa sorte com sua busca!

Stijn de Witt
fonte
2
Ótimo post Stijn! Você recomendaria usar um kit inicial para um aplicativo de reação isomórfica? Se sim, você poderia dar um exemplo de um que preferiria?
Chris
1
@ Paulos3000 Depende do servidor que você está usando. Basicamente, você define uma rota /*e faz com que ela responda com sua página HTML. O mais complicado aqui é garantir que você não intercepte solicitações para os arquivos .js e .css nessa rota.
Stijn de Witt
2
@ Paulos3000 Veja aqui algumas perguntas relacionadas: para Apache / php , para Express / js , para J2E / Java .
Stijn de Witt
1
Oi, estou tentando a solução de hash, no entanto, sem sorte. Obtendo createMemoryHistory is not a functionerro. Importa-se de olhar? stackoverflow.com/questions/45002573/…
Leon Gaban
5
@LeonGaban Parece que desde que esta resposta foi escrita, o React Router mudou sua implementação. Agora eles têm instâncias diferentes do roteador para os diferentes históricos e fazem a configuração do histórico em segundo plano. Os princípios básicos ainda são os mesmos.
Stijn de Witt
116

As respostas aqui são extremamente úteis, o que funcionou para mim foi configurar meu servidor Webpack para esperar as rotas.

devServer: {
   historyApiFallback: true,
   contentBase: './',
   hot: true
},

O historyApiFallback foi o que corrigiu esse problema para mim. Agora, o roteamento funciona corretamente e eu posso atualizar a página ou digitar o URL diretamente. Não há necessidade de se preocupar com soluções alternativas no servidor do nó. Obviamente, essa resposta só funciona se você estiver usando o webpack.

EDIT: veja minha resposta aqui para uma razão mais detalhada por que isso é necessário: https://stackoverflow.com/a/37622953/5217568

jmancherje
fonte
16
Observe que a equipe do Webpack recomenda não usar o servidor de desenvolvimento na produção.
Stijn de Witt
5
Para fins de desenvolvimento genérico, esta é a melhor solução. historyApiFallbacké suficiente. Como para todas as outras opções, também pode ser definido na CLI com o sinalizador --history-api-fallback.
Marco Lazzeri 27/10
2
@Kunok Isso não acontece. Essa é uma solução rápida para o desenvolvimento, mas você ainda precisará descobrir algo para a produção.
Stijn de Witt
contentBase: ': /' porque os arquivos do aplicativo podem ser acessados ​​a partir do URL
Nezih 21/02
85

Você pode alterar seu .htaccessarquivo e inserir o seguinte:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-l
  RewriteRule . /index.html [L]
</IfModule>

Estou usando react: "^16.12.0"e react-router: "^5.1.2" Esse método é o Catch-all e provavelmente é a maneira mais fácil de começar.

BrahimS
fonte
3
Isso funciona bem! e mais fácil se você não quer reestruturar a sua aplicação
mhyassin
7
Não se esqueça RewriteEngine On como a primeira linha
Kai Qing
3
Observe que esta resposta está se referindo à configuração em um servidor Apache. Isso não faz parte de um aplicativo React.
Colton Hicks
Não entendi as respostas acima. Nenhum dos tutorriais disse que meus links não funcionariam ao vivo. No entanto, este arquivo htaccess simples funcionou. Obrigado
Thomas Williams
Isso também funciona para a exportação estática do next.js. Como o tomcat não é semelhante ao nó, precisamos dessa configuração adicional para a exportação estática do next.js.
giri-jeedigunta 4/04
62

Para React Router V4 usuários do :

Se você tentar resolver esse problema pela técnica do Hash History mencionada em outras respostas, observe que

<Router history={hashHistory} >

não funciona na V4, use HashRouter:

import { HashRouter } from 'react-router-dom'

<HashRouter>
  <App/>
</HashRouter>

Referência: HashRouter

user2875289
fonte
Você poderia dar detalhes sobre o motivo pelo qual o HashRouter corrige esse problema? O link que você forneceu não explica para mim. Além disso, existe uma maneira de ocultar o hash no caminho? Eu estava usando o BrowserRouter, mas me deparei com este problema 404 ..
Ian Smith
29

O roteador pode ser chamado de duas maneiras diferentes, dependendo se a navegação ocorre no cliente ou no servidor. Você o configurou para operação no lado do cliente. O parâmetro key é o segundo no método run , a localização.

Quando você usa o componente React Router Link, ele bloqueia a navegação do navegador e chama transição para fazer uma navegação no lado do cliente. Você está usando o HistoryLocation; portanto, ele usa a API de histórico do HTML5 para concluir a ilusão de navegação, simulando o novo URL na barra de endereços. Se você estiver usando navegadores mais antigos, isso não funcionará. Você precisaria usar o componente HashLocation.

Quando você pressiona a atualização, ignora todo o código React e React Router. O servidor recebe a solicitação /jobliste deve retornar algo. No servidor, você precisa passar o caminho que foi solicitado ao runmétodo para que ele renderize a exibição correta. Você pode usar o mesmo mapa de rotas, mas provavelmente precisará de uma chamada diferente paraRouter.run . Como Charles aponta, você pode usar a reescrita de URL para lidar com isso. Outra opção é usar um servidor node.js. para manipular todas as solicitações e passar o valor do caminho como o argumento de localização.

No expresso, por exemplo, pode ser assim:

var app = express();

app.get('*', function (req, res) { // This wildcard method handles all requests

    Router.run(routes, req.path, function (Handler, state) {
        var element = React.createElement(Handler);
        var html = React.renderToString(element);
        res.render('main', { content: html });
    });
});

Observe que o caminho da solicitação está sendo passado para run. Para fazer isso, você precisará de um mecanismo de exibição no lado do servidor para o qual possa passar o HTML renderizado. Existem várias outras considerações sobre o uso renderToStringe a execução do React no servidor. Depois que a página é renderizada no servidor, quando o aplicativo é carregado no cliente, ele é renderizado novamente, atualizando o HTML renderizado no servidor, conforme necessário.

Todd
fonte
2
desculpe-me, você pode explicar pls a próxima declaração "quando você pressiona atualizar, ignora todo o código do React e do React Router"? Por que isso acontece?
VB_
O roteador de reação é normalmente usado para manipular 'caminhos' diferentes apenas dentro do navegador. Há duas maneiras comuns de fazer isso: o caminho de hash do estilo antigo e a API de histórico mais recente. Uma atualização do navegador fará uma solicitação do servidor, que ignorará o código do roteador de reação do lado do cliente. Você precisará manipular o caminho no servidor (mas apenas se estiver usando a API do histórico). Você pode usar o roteador de reação para isso, mas precisará fazer algo semelhante ao que descrevi acima.
Todd
Entendi. Mas como deve ser se o servidor estiver na linguagem não JS (digamos Java)?
VB_
2
desculpe ... quer dizer que você precisa de um servidor expresso para renderizar uma rota "não raiz"? Fiquei surpreso ao saber que a definição do isomorfismo não incluía a busca de dados em seu conceito (as coisas são desesperadamente complexas se você deseja exibir o WITH com os servidores de dados, embora seja uma necessidade óbvia de SEO - e o isomorfismo afirma isso). Agora estou tipo "WTF reage", sério ... Corrija-me se estiver errado, o ember / angular / backbone exige algo de servidor para renderizar uma rota? Eu realmente não entendo essa exigência inchado às rotas de uso
Ben
1
Isso significa que a atualização no reat-router não funciona se meu aplicativo de reação não for isomórfico?
Matt
28

No seu index.html head, adicione o seguinte:

<base href="/">
<!-- This must come before the css and javascripts -->

Então, ao executar com o servidor de desenvolvimento webpack, use este comando.

webpack-dev-server --mode development --hot --inline --content-base=dist --history-api-fallback

--history-api-fallback é a parte importante

Efe Ariaroo
fonte
Isso funcionou muito bem! Algum link para ler um pouco mais sobre como você conseguiu / encontrou essa solução?
justDan
1
Desculpe mano. Encontrei-o através da técnica comprovada de tentativa e erro.
Efe Ariaroo
Pena de mencionar que se você estiver usando a href de base que não é /, então HashRouternão vai funcionar (se você estiver usando hash de roteamento)
Robbie Averill
A tag <base href = "/"> fez isso por mim. Obrigado por isso. O servidor de desenvolvimento Webpack continuou tentando pegar o pacote do caminho / <pacote> e estava falhando.
Malisbad 26/03/19
27

Eu usei o create-react-app para criar um site agora e teve o mesmo problema apresentado aqui. Eu uso BrowserRoutingdo react-router-dompacote. Estou executando em um servidor Nginx e o que o resolveu foi adicionar o seguinte ao/etc/nginx/yourconfig.conf

location / {
  if (!-e $request_filename){
    rewrite ^(.*)$ /index.html break;
  }
}

O que corresponde a adicionar o seguinte ao .htaccesscaso de você estar executando o Appache

Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]

Essa também parece ser a solução sugerida pelo próprio Facebook e pode ser encontrada aqui

Auxílio em
fonte
1
Uau, uma solução tão simples para o nginx; funciona como um encanto, como um novo usuário do nginx. Basta copiar e colar e pronto. +1 para vincular à documentação oficial do fb. Caminho a percorrer.
user3773048
Para constar: estou usando uma instância da AWS com nginx e um aplicativo angular. Isso funciona.
Diego Sarmiento
você me salvou. Eu estava tentando corrigir o meu problema de ontem eu cansei algumas soluções, mas eu falhei. finalmente, você me ajudou a resolver esse problema do roteador. ah muito obrigado irmão
Sharifur Robin
Existe algo diferente necessário para "react": "^ 16.8.6", "react-router": "^ 5.0.1"? Eu tentei essas soluções (nginx e apache) e elas não funcionam.
Mark A. Tagliaferro
Como o Facebook não alterou sua documentação sobre isso, só posso assumir que o procedimento é o mesmo. Eu não tentei há um tempo embora.
Aidin
17

Isso pode resolver seu problema

Também enfrentei o mesmo problema no aplicativo ReactJS no modo de produção. Aqui está a solução 2 para o problema.

1.Altere o histórico de roteamento para "hashHistory" em vez de browserHistory no lugar de

<Router history={hashHistory} >
   <Route path="/home" component={Home} />
   <Route path="/aboutus" component={AboutUs} />
</Router>

Agora crie o aplicativo usando o comando

sudo npm run build

Em seguida, coloque a pasta build na sua pasta var / www /. Agora, o aplicativo está funcionando bem com a adição da tag # em cada URL. gostar

localhost / # / home localhost / # / aboutus

Solução 2: sem a tag # usando o browserHistory,

Defina seu history = {browserHistory} em seu roteador, agora construa-o usando sudo npm run build.

Você precisa criar o arquivo "conf" para resolver a página 404 não encontrada, o arquivo conf deve ser assim.

abra o seu terminal digite os comandos abaixo

cd / etc / apache2 / sites-available ls nano sample.conf Adicione o conteúdo abaixo.

<VirtualHost *:80>
    ServerAdmin admin@0.0.0.0
    ServerName 0.0.0.0
    ServerAlias 0.0.0.0
    DocumentRoot /var/www/html/

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    <Directory "/var/www/html/">
            Options Indexes FollowSymLinks
            AllowOverride all
            Require all granted
    </Directory>
</VirtualHost>

Agora você precisa ativar o arquivo sample.conf usando o seguinte comando

cd /etc/apache2/sites-available
sudo a2ensite sample.conf

então ele solicitará que você recarregue o servidor apache, usando o serviço sudo apache2, recarregue ou reinicie

abra a pasta localhost / build e adicione o arquivo .htaccess com o conteúdo abaixo.

   RewriteEngine On
   RewriteBase /
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteCond %{REQUEST_FILENAME} !-l
   RewriteRule ^.*$ / [L,QSA]

Agora o aplicativo está funcionando normalmente.

Nota: altere 0.0.0.0 ip para o seu endereço IP local.

Se houver alguma dúvida, sinta-se à vontade para comentar.

Espero que seja útil para os outros.

Venkatesh Somu
fonte
A primeira solução funcionará para "react-router-dom": "^5.1.2"?, Estou usando<BrowserRouter>
151291
16

Se você estiver hospedando um aplicativo de reação via AWS Static S3 Hosting & CloudFront

Esse problema foi apresentado pelo CloudFront, respondendo com uma mensagem 403 Access Denied, porque esperava que algum / outro / caminho existisse na minha pasta S3, mas esse caminho existe apenas internamente no roteamento do React com o roteador de reação.

A solução foi configurar uma regra de páginas de erro de distribuição. Vá para as configurações do CloudFront e escolha sua distribuição. Em seguida, vá para a guia "Páginas de erro". Clique em "Criar resposta de erro personalizada" e adicione uma entrada para 403, pois esse é o código de status de erro que obtemos. Defina o caminho da página de resposta como /index.html e o código de status como 200. O resultado final me surpreende com sua simplicidade. A página de índice é veiculada, mas o URL é preservado no navegador; portanto, quando o aplicativo de reação é carregado, ele detecta o caminho do URL e navega para a rota desejada.

Regra de páginas 403 de erro

th3morg
fonte
1
Isso é exatamente o que eu precisava. Obrigado.
Jonathan
Então .. todos os seus URLs funcionam, mas retornam o código de status 403? Não está correto. Os códigos de status esperados devem estar no intervalo 2xx.
Stijn de Witt
@StijndeWitt Não sei se você está entendendo corretamente. O CloudFront cuidará de tudo - o cliente não verá nenhum código de status 403. O roteamento ocorre do lado do servidor, renderiza a página de índice, mantém a URL e fornece a rota correta como resultado.
Th3morg 22/10/19
@ th3morg Ah sim, eu vejo agora. Perdi que ele também permite que você preencha o código de status de resposta como 200. Então, basicamente, você está mapeando o status 403 para o status 200 ... Parece bom ... exceto que talvez se você obtiver 403 como resultado devido a algum outro motivo você não poderá vê-lo por causa disso.
Stijn de Witt
1
obrigado por este @ th3morg. Essa configuração também funciona para caminhos de vários níveis? Funciona muito bem para mim se houver algum caminho no nível raiz, como domínio / inscrição, domínio / login, domínio / <documentId>, mas não funciona para caminhos de vários níveis, como domínio / documento / <documentId>.
Pargles 03/02
16

O Webpack Dev Server tem uma opção para habilitar isso. Abra package.jsone adicione --history-api-fallback. Essas soluções funcionaram para mim.

Tutorial do react-router

srijishks
fonte
3
Não tem problema, webpack-dev-servermas como faço para corrigir isso na construção da produção?
Hussain
12

Se você estiver hospedando seu aplicativo de reação no IIS, basta adicionar um arquivo web.config contendo:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404" subStatusCode="-1" />
        <error statusCode="404" path="/" responseMode="ExecuteURL" />
    </httpErrors>
  </system.webServer>
</configuration>

Isso instruirá o servidor IIS a retornar a página principal ao cliente, em vez do erro 404, sem a necessidade de usar o histórico de hash.

Chtiwi Malek
fonte
obrigado mano !!!! isso funciona!
Thanh Bao
12

Adicione isto a webpack.config.js:

devServer: {
    historyApiFallback: true
}
suraj
fonte
Isso é ótimo para desenvolvedores, mas não ajuda na criação de produção.
KFunk
11

Pilha de produção: React, React Router v4, BrowswerRouter, Express, Nginx

1) Navegador do usuárioRouter para URLs bonitas

// app.js

import { BrowserRouter as Router } from 'react-router-dom'

const App = () {
  render() {
    return (
        <Router>
           // your routes here
        </Router>
    )
  }
}

2) Adicione index.html a todas as solicitações desconhecidas usando /*

// server.js

app.get('/*', function(req, res) {   
  res.sendFile(path.join(__dirname, 'path/to/your/index.html'), function(err) {
    if (err) {
      res.status(500).send(err)
    }
  })
})

3) empacote o webpack com webpack -p

4) correr nodemon server.jsounode server.js

EDIT: você pode deixar o nginx lidar com isso no bloco do servidor e desconsiderar a etapa 2:

location / {
    try_files $uri /index.html;
}
Isaac Pak
fonte
ficando caminho da indefinido
Goutham
@Goutham Na parte superior do seu arquivo server.js, adicione import path from 'pathouconst path = require('path')
Isaac Pak
o passo 2 (pegue tudo com /*) acabou de salvar meu dia. Obrigado! :)
Atlas7
Isso funciona, para as pessoas que utilizam redux e preocupado com um roteador conectado veja este exemplo: github.com/supasate/connected-react-router/tree/master/examples/...
cjjenkinson
10

Se você estiver usando o Create React App:

No entanto, há uma grande caminhada sobre esse problema com soluções para muitas das principais plataformas de hospedagem que você pode encontrar AQUI na página Criar aplicativo do React Por exemplo, eu uso o React Router v4 e o Netlify para o meu código de front-end. Bastava adicionar 1 arquivo à minha pasta pública ("_redirects") e uma linha de código nesse arquivo:

/*  /index.html  200

Agora, meu site cria corretamente caminhos como mysite.com/pricing quando inserido no navegador ou quando alguém acessa a atualização.

Colton Hicks
fonte
7

Tente adicionar o arquivo ".htaccess" dentro da pasta pública com o código abaixo.

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]

RewriteRule ^ /index.html [L]  
Kiran Joshi
fonte
Obrigado, foi isso que funcionou para mim no Fedora 28 com apache. Nenhuma das regras de reescrita acima nem as da página CRA funcionaram para mim. Eu os adicionei à configuração do host virtual em vez de em um arquivo .htaccess separado.
boerre
6

Se você tem um substituto para o seu index.html, verifique se no seu arquivo index.html você tem este:

<script>
  System.config({ baseURL: '/' });
</script>

Isso pode diferir de projeto para projeto.

Matt Goo
fonte
2
Adicione isso ao seu html head: <base href = "/">
Efe Ariaroo
4

Se você estiver usando o firebase, tudo o que você precisa fazer é garantir que você tenha uma propriedade reescrita no arquivo firebase.json na raiz do seu aplicativo (na seção de hospedagem).

Por exemplo:

{ 
  "hosting": {
    "rewrites": [{
      "source":"**",
      "destination": "/index.html"
    }]    
  }
}

Espero que isso poupe a alguém um tesouro de frustração e perda de tempo.

Feliz codificação ...

Leitura adicional sobre o assunto:

https://firebase.google.com/docs/hosting/full-config#rewrites

CLI do Firebase: "Configure como um aplicativo de página única (reescreva todos os URLs em /index.html)"

Joshua Dyck
fonte
Você é uma lenda
Perniferous
4

Encontrei a solução para o meu SPA com o roteador de reação (Apache). Basta adicionar .htaccess

<IfModule mod_rewrite.c>

  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-l
  RewriteRule . /index.html [L]

</IfModule>

fonte: https://gist.github.com/alexsasharegan/173878f9d67055bfef63449fa7136042

Sasha Ignashevich
fonte
3

Ainda não estou usando a renderização no servidor, mas encontrei o mesmo problema do OP, em que o Link parecia funcionar bem na maioria das vezes, mas falhava quando tinha um parâmetro. Documentarei minha solução aqui para ver se isso ajuda alguém.

Meu jsx principal contém isso:

<Route onEnter={requireLogin} path="detail/:id" component={ModelDetail} />

Isso funciona bem para o primeiro link correspondente, mas quando o: id é alterado <Link> expressões aninhadas na página de detalhes desse modelo, o URL é alterado na barra do navegador, mas o conteúdo da página não foi alterado inicialmente para refletir o modelo vinculado.

O problema era que eu tinha usado o props.params.idpara definir o modelo componentDidMount. O componente é montado apenas uma vez, o que significa que o primeiro modelo é o que fica na página e os Links subsequentes alteram os acessórios, mas deixam a página inalterada.

Definir o modelo no estado do componente em componentDidMounte em componentWillReceiveProps(onde é baseado nos próximos objetos) resolve o problema e o conteúdo da página é alterado para refletir o modelo desejado.

Paul Whipp
fonte
1
Talvez seja melhor usar o construtor de componentes (que também tem acesso props) iso componentDidMountse você quiser tentar a renderização do lado do servidor. Porque componentDidMounté chamado apenas no navegador. Seu objetivo é fazer coisas com o DOM, como anexar ouvintes de eventos a bodyetc. que você não pode fazer render.
Stijn de Witt
3

Este tópico é um pouco antigo e resolvido, mas eu gostaria de sugerir uma solução simples, clara e melhor. Funciona se você usar um servidor web.

Cada servidor da Web tem a capacidade de redirecionar o usuário para uma página de erro no caso de http 404. Para resolver esse problema, você precisa redirecionar o usuário para a página de índice.

Se você usar o servidor base Java (tomcat ou qualquer servidor de aplicativos java), a solução poderá ser a seguinte:

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- WELCOME FILE LIST -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- ERROR PAGES DEFINITION -->
    <error-page>
        <error-code>404</error-code>
        <location>/index.jsp</location>
    </error-page>

</web-app>

Exemplo:

  • GET http://example.com/about
  • O servidor da Web lança http 404 porque esta página não existe no lado do servidor
  • a configuração da página de erro informa ao servidor que envia a página index.jsp de volta ao usuário
  • JS fará o resto do trabalho no lado do cliente, porque o URL no lado do cliente ainda é http://example.com/about .

É isso, não há mais necessidade mágica :)

zappee
fonte
Isso foi demais! Estou usando o servidor Wildfly 10.1 e fiz essa atualização no meu arquivo web.xml, exceto que o local estava definido como '/'. Isso ocorreu porque no meu código de reação eu usei o histórico do navegador como este:const browserHistory = useRouterHistory(createHistory)({ basename: '/<appname>' });
Chuck L
1
Tem certeza de que isso não afeta o SEO? Você está atribuindo um status 404 a páginas que realmente existem. O usuário pode nunca perceber isso, mas os bots prestam muita atenção, de fato, tanto que não rasparão sua página.
subharb
3

Se você estiver usando o Express ou alguma outra estrutura no back-end, poderá adicionar a configuração semelhante à abaixo e verificar o caminho público do Webpack na configuração. Ele funcionará bem mesmo ao recarregar se você estiver usando o BrowserRouter

expressApp.get('/*', (request, response) => {
    response.sendFile(path.join(__dirname, '../public/index.html'));
});
user2849063
fonte
Esta é a solução mais simples. note que essa rota deve ir depois de quaisquer outras rotas, como é uma captura tudo
dcsan
3

Corrigindo o erro "não é possível obter / URL" na atualização ou na chamada direta do URL.

Configure o seu webpack.config.js para esperar o link fornecido nas rotas como esta.

module.exports = {
  entry: './app/index.js',
  output: {
       path: path.join(__dirname, '/bundle'),
       filename: 'index_bundle.js',
       publicPath: '/'
  },
Lalit Tyagi
fonte
2

Como estou usando o .Net Core MVC, algo como isto me ajudou:

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            var url = Request.Path + Request.QueryString;
            return App(url);
        }

        [Route("App")]
        public IActionResult App(string url)
        {
            return View("/wwwroot/app/build/index.html");
        }
   }

Basicamente, no lado do MVC, todas as rotas que não corresponderem cairão Home/Indexconforme especificado em startup.cs. No interior Index, é possível obter o URL da solicitação original e passá-lo sempre que necessário.

startup.cs

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");

            routes.MapSpaFallbackRoute(
                name: "spa-fallback",
                defaults: new { controller = "Home", action = "Index" });
        });
Elnoor
fonte
Se a API da Web estiver separada do aplicativo, como você lida com isso?
dayanrr91
@ dayanrr91 se o seu projeto tiver API Web, você não precisará de roteamento do lado do servidor. E seu reag-router, acredito, deve ler o URL e fazer o roteamento apropriado. Nada deve bloqueá-lo #
1100 Elnoor
seu comentário é muito apreciado, mas, em seguida, tenho uma pergunta, acabei de adicionar o HashRouter, mas agora, quando entro em qualquer URL manualmente, ele será redirecionado para o meu componente home em vez de redirecionar para minha casa e depois para o componente que eu estava tentando entrando, existe alguma solução alternativa para isso?
dayanrr91
@ dayanrr91 realmente não faço ideia, nunca tentei o hashrouter, mas acho que deve ter algo a ver com o seu código. A maneira como o hashrouter funciona deve ser semelhante ao browserrouter. Também testei aqui nesta sandbox, ele funciona bem codesandbox.io/s/25okp1mny , teste o URL 25okp1mny.codesandbox.io/#/roster/5
Elnoor
2

Se você está hospedando no IIS; Adicionando isso ao meu webconfig resolveu meu problema

<httpErrors errorMode="Custom" defaultResponseMode="ExecuteURL">
    <remove statusCode="500" subStatusCode="100" />
    <remove statusCode="500" subStatusCode="-1" />
    <remove statusCode="404" subStatusCode="-1" />
    <error statusCode="404" path="/" responseMode="ExecuteURL" />
    <error statusCode="500" prefixLanguageFilePath="" path="/error_500.asp" responseMode="ExecuteURL" />
    <error statusCode="500" subStatusCode="100" path="/error_500.asp" responseMode="ExecuteURL" />
</httpErrors>

Você pode fazer configurações semelhantes para qualquer outro servidor

Barny
fonte
1

Caso alguém esteja aqui procurando uma solução no React JS SPA com o Laravel. A resposta aceita é a melhor explicação de por que esses problemas acontecem. Como já explicado, você deve configurar o lado do cliente e o servidor. No modelo do seu blade, inclua o arquivo compactado js, ​​use-o URL facadedesta maneira

<script src="{{ URL::to('js/user/spa.js') }}"></script>

Em suas rotas, adicione-o ao terminal principal onde está o modelo blade. Por exemplo,

Route::get('/setting-alerts', function () {
   return view('user.set-alerts');
});

O acima é o ponto final principal do modelo blade. Agora adicione também uma rota opcional,

Route::get('/setting-alerts/{spa?}', function () {
  return view('user.set-alerts');
});

O problema que acontece é que primeiro o modelo blade é carregado e depois o roteador de reação. Então, quando você está carregando '/setting-alerts', ele carrega o html e os js. Mas quando você carrega '/setting-alerts/about', ele primeiro carrega no lado do servidor. Como no lado do servidor, não há nada nesse local, ele retorna não encontrado. Quando você possui esse roteador opcional, ele carrega a mesma página e o roteador de reação também é carregado; o carregador de reatores decide qual componente exibir. Espero que isto ajude.

Koushik Das
fonte
É possível carregar em um URI exato no servidor e o servidor redireciona para o React? Por exemplo, quando carrego /user/{userID}, só preciso retornar uma /userexibição universal em HTML, trazer o ID e chamá-lo usando AJAX ou axios. É possível fazer isso? eles são amigáveis ​​com SEO?
Ryuujo 22/02/19
1

Para aqueles que usam o IIS 10, é isso que você deve fazer para corrigir isso. Certifique-se de estar usando o browserHistory com isso. Quanto à referência, darei o código para o roteamento, mas não é isso que importa, o que importa é o próximo passo após o código do componente abaixo:

class App extends Component {
    render() {
        return (
            <Router history={browserHistory}>
                <div>
                    <Root>
                        <Switch>
                            <Route exact path={"/"} component={Home} />    
                            <Route path={"/home"} component={Home} />
                            <Route path={"/createnewproject"} component={CreateNewProject} />
                            <Route path={"/projects"} component={Projects} />
                            <Route path="*" component={NotFoundRoute} />
                        </Switch>
                    </Root>
                </div>
            </Router>
        )
    }
}
render (<App />, window.document.getElementById("app"));

Como o problema é que o IIS recebe uma solicitação dos navegadores clientes, ele interpretará a URL como se estivesse solicitando uma página e retornará uma página 404, pois não há uma página disponível. Faça o seguinte:

  1. Abra o IIS
  2. Expanda Servidor e abra a Pasta Sites
  3. Clique no site / aplicativo
  4. Vá para as páginas de erro
  5. Abra o item de status de erro 404 na lista
  6. Em vez da opção "Inserir conteúdo do arquivo estático na resposta de erro", altere-o para "Executar um URL neste site" e adicione "/" o valor da barra ao URL.

E agora vai funcionar bem.

insira a descrição da imagem aqui insira a descrição da imagem aqui

Espero que ajude. :-)

Martin Lloyd Jose
fonte
1

Estou usando o WebPack, tive o mesmo problema Solução => No seu arquivo server.js

const express = require('express');
const app = express();

app.use(express.static(path.resolve(__dirname, '../dist')));
  app.get('*', function (req, res) {
    res.sendFile(path.resolve(__dirname, '../dist/index.html'));
    // res.end();
  });

Por que meu aplicativo não é renderizado após a atualização?

Vishal Singh
fonte
Isso fez por mim! Inicialmente eu tinha res.sendFile (path.JOIN (publicPath, "index.html")); Alterei "junção" para "resolvido", como no exemplo acima, para: res.sendFile (path.resolve ("./ dist", "index.html")); Eu também estava brincando com __dirname, mas realmente não conseguia entendê-lo ou fazê-lo funcionar, então copiei manualmente em "./dist" porque é daí que é servido meu index.html. Também declarei isso assim: app.use (express.static ("./ dist"));
logixplayer
1

Usando HashRoutertrabalhou para mim com Redux também, basta substituir:

import {
  Router //replace Router
} from "react-router-dom";

ReactDOM.render(
    <LocaleProvider locale={enUS}>
    <Provider store={Store}>
        <Router history={history}> //replace here saying Router
            <Layout/>
        </Router>
    </Provider>
</LocaleProvider>, document.getElementById("app"));
registerServiceWorker();

para:

import {
  HashRouter //replaced with HashRouter
} from "react-router-dom";

ReactDOM.render(
    <LocaleProvider locale={enUS}>
    <Provider store={Store}>
        <HashRouter history={history}> //replaced with HashRouter
            <Layout/>
        </HashRouter>
    </Provider>
</LocaleProvider>, document.getElementById("app"));
registerServiceWorker();
Alireza
fonte
0

Eu tive esse mesmo problema e essa solução funcionou para nós ..

Fundo:

Estamos hospedando vários aplicativos no mesmo servidor. Quando atualizávamos, o servidor não entendia onde procurar nosso índice na pasta dist para esse aplicativo específico. O link acima o levará ao que funcionou para nós ... Espero que isso ajude, pois passamos muitas horas tentando descobrir uma solução para nossas necessidades.

Nós estamos usando:

package.json

"dependencies": {
"babel-polyfill": "^6.23.0",
"ejs": "^2.5.6",
"express": "^4.15.2",
"prop-types": "^15.5.6",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-redux": "^5.0.4",
"react-router": "^3.0.2",
"react-router-redux": "^4.0.8",
"redux": "^3.6.0",
"redux-persist": "^4.6.0",
"redux-thunk": "^2.2.0",
"webpack": "^2.4.1"
}

meu webpack.config.js

webpack.config.js

/* eslint-disable */
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const babelPolyfill = require('babel-polyfill');
const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
  template: __dirname + '/app/views/index.html',
  filename: 'index.html',
  inject: 'body'
});

module.exports = {
  entry: [
    'babel-polyfill', './app/index.js'
  ],
  output: {
    path: __dirname + '/dist/your_app_name_here',
    filename: 'index_bundle.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      loader: 'babel-loader',
      query : {
          presets : ["env", "react", "stage-1"]
      },
      exclude: /node_modules/
    }]
  },
  plugins: [HTMLWebpackPluginConfig]
}

meu index.js

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import Routes from './Routes'
import { Provider } from 'react-redux'
import { createHistory } from 'history'
import { useRouterHistory } from 'react-router'
import configureStore from './store/configureStore'
import { syncHistoryWithStore } from 'react-router-redux'
import { persistStore } from 'redux-persist'

const store = configureStore();

const browserHistory = useRouterHistory(createHistory) ({
  basename: '/your_app_name_here'
})
const history = syncHistoryWithStore(browserHistory, store)

persistStore(store, {blacklist: ['routing']}, () => {
  console.log('rehydration complete')
})
// persistStore(store).purge()


ReactDOM.render(
    <Provider store={store}>
      <div>
        <Routes history={history} />
      </div>
    </Provider>,
  document.getElementById('mount')
)

meu app.js

var express = require('express');
var app = express();

app.use(express.static(__dirname + '/dist'));
// app.use(express.static(__dirname + '/app/assets'));
app.set('views', __dirname + '/dist/your_app_name_here');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

app.get('/*', function (req, res) {
    res.render('index');
});

app.listen(8081, function () {
  console.log('MD listening on port 8081!');
});
J. Parrish
fonte
0

Eu gosto dessa maneira de lidar com isso. Tente adicionar: yourSPAPageRoute / * no lado do servidor para se livrar desse problema.

Eu segui essa abordagem porque mesmo a API de histórico HTML5 nativa não suporta o redirecionamento correto na atualização da página (tanto quanto eu sei).

Nota: A resposta selecionada já abordou isso, mas estou tentando ser mais específica.

Rota expressa

API de teste - histórico Testado e só queria compartilhar isso.

Espero que ajude.

Rehan
fonte