Adresy URL routera React nie działają podczas ręcznego odświeżania lub pisania


653

Używam routera React i działa dobrze, kiedy klikam przyciski łącza, ale po odświeżeniu strony nie ładuje się, co chcę.

Na przykład jestem w localhost/joblistśrodku i wszystko jest w porządku, ponieważ przybyłem tutaj, naciskając link. Ale jeśli odświeżę stronę, otrzymam:

Cannot GET /joblist

Domyślnie tak nie działało. Początkowo miałem URL jak localhost/#/i localhost/#/joblisti pracowali perfekcyjnie. Ale nie podoba mi się ten rodzaj adresu URL, więc próbując go usunąć #, napisałem:

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

Ten problem nie występuje localhost/, ten zawsze zwraca to, czego chcę.

EDYCJA: Ta aplikacja jest jednostronicowa, więc /joblistnie trzeba nic pytać na żadnym serwerze.

EDYCJA 2: Cały mój router.

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);
});

chyba że użyjesz htaccess do załadowania głównej strony z trasami i powiesz routerowi, aby używał location.pathname, to nie zadziała.
Charles John Thompson III

Jak usunąłeś ten #symbol? Dziękuję Ci!
SudoPlz

4
Jeśli przechowujesz swoją aplikację reagującą w segmencie S3, możesz po prostu ustawić dokument błędu na index.html. Zapewni index.htmlto trafienie bez względu na wszystko.
Trevor Hutto

W moim przypadku działa dobrze w
systemie

To jest odniesienie, które pomogło rozwiązać mój problem: github.com/facebook/create-react-app/blob/master/packages/…
jimbotron

Odpowiedzi:


1094

Patrząc na komentarze do zaakceptowanej odpowiedzi i ogólny charakter tego pytania („nie pracuj”), pomyślałem, że może to być dobre miejsce na kilka ogólnych wyjaśnień na temat związanych z tym problemów. Tak więc ta odpowiedź ma stanowić dodatkowe informacje / opracowanie dotyczące konkretnego przypadku zastosowania PO. Proszę o wyrozumiałość.

Po stronie serwera a po stronie klienta

Pierwszą ważną rzeczą, którą należy zrozumieć, jest to, że są teraz 2 miejsca, w których adres URL jest interpretowany, podczas gdy kiedyś było tylko 1 w „dawnych czasach”. W przeszłości, gdy życie było proste, niektórzy użytkownicy wysyłali żądania http://example.com/aboutdo serwera, który sprawdzał część adresu URL ścieżki, ustalał, że użytkownik prosi o stronę z informacjami, a następnie odsyła tę stronę.

Dzięki routingowi po stronie klienta, który zapewnia React-Router, rzeczy są mniej proste. Początkowo klient nie ma jeszcze załadowanego kodu JS. Tak więc pierwsze żądanie zawsze będzie skierowane do serwera. Zwróci to stronę zawierającą potrzebne znaczniki skryptu, aby załadować React i React Router itp. Dopiero po załadowaniu tych skryptów rozpoczyna się faza 2. W fazie 2, gdy użytkownik kliknie na przykład link nawigacyjny „O nas”, adres URL jest zmieniany tylko lokalnie na http://example.com/about(możliwe dzięki History API ), ale nie jest wysyłane żadne żądanie do serwera. Zamiast tego React Router działa po stronie klienta, określa, który widok React ma być renderowany i renderuje go. Zakładając, że strona o stronie nie musi wykonywać żadnych wywołań REST, jest już zrobiona. Zmieniłeś się z Home do About About bez zwolnienia żądania serwera.

Zasadniczo po kliknięciu łącza niektóre skrypty JavaScript zmieniają adres URL na pasku adresu, nie powodując odświeżania strony , co z kolei powoduje, że React Router wykonuje przejście strony po stronie klienta .

Ale teraz zastanów się, co się stanie, jeśli skopiujesz i wkleisz adres URL w pasku adresu i wyślesz go e-mailem do znajomego. Twój przyjaciel nie załadował jeszcze Twojej witryny. Innymi słowy, wciąż znajduje się w fazie 1 . Na jej komputerze nie działa jeszcze React Router. Więc jej przeglądarka zrobi wysyła żądanie serwera do http://example.com/about.

I tu zaczynają się twoje problemy. Do tej pory można było uniknąć umieszczania statycznego kodu HTML w katalogu głównym serwera. Ale to dawałoby 404błędy dla wszystkich innych adresów URL na żądanie z serwera . Te same adresy URL działają dobrze po stronie klienta , ponieważ tam React Router wykonuje routing za Ciebie, ale zawodzą po stronie serwera, chyba że sprawisz, że serwer je zrozumie.

Łączenie routingu po stronie serwera i klienta

Jeśli chcesz http://example.com/about adres URL działał zarówno po stronie serwera, jak i klienta, musisz skonfigurować trasy dla niego zarówno po stronie serwera, jak i klienta. Czy ma to sens, prawda?

I tu zaczynają się twoje wybory. Rozwiązania obejmują obejście problemu całkowicie, poprzez trasę typu catch-all, która zwraca kod ładujący HTML, aż do pełnego podejścia izomorficznego, w którym zarówno serwer, jak i klient uruchamiają ten sam kod JS.

.

Całkowite obejście problemu: Historia mieszania

W przypadku Historii mieszania zamiast Historii przeglądarki adres URL strony z informacjami mógłby wyglądać mniej więcej tak: http://example.com/#/about Część po #symbolu skrótu ( ) nie jest wysyłana na serwer. Tak więc serwer widzi http://example.com/i wysyła stronę indeksu zgodnie z oczekiwaniami. React-Router odbierze #/aboutczęść i wyświetli prawidłową stronę.

Wady :

  • „brzydkie” adresy URL
  • Przy takim podejściu renderowanie po stronie serwera nie jest możliwe. Jeśli chodzi o Search Engine Optimization (SEO), twoja strona składa się z jednej strony, na której nie ma prawie żadnych treści.

.

Złap wszystkie

Dzięki takiemu podejściu używasz historii przeglądarki, ale wystarczy założyć catch-all na serwerze, który wysyła /*do index.html, skutecznie daje dużo takiej samej sytuacji jak z Hash Historii. Masz jednak czyste adresy URL i możesz ulepszyć ten schemat później, bez konieczności unieważniania wszystkich ulubionych użytkowników.

Wady :

  • Bardziej skomplikowany w konfiguracji
  • Nadal nie ma dobrego SEO

.

Hybrydowy

W podejściu hybrydowym rozwijasz scenariusz catch-all, dodając określone skrypty dla określonych tras. Możesz wykonać kilka prostych skryptów PHP, aby zwrócić najważniejsze strony witryny z zawartością, aby Googlebot mógł przynajmniej zobaczyć, co jest na twojej stronie.

Wady :

  • Jeszcze bardziej skomplikowany w konfiguracji
  • Tylko dobre SEO dla tych tras, które dajesz specjalnemu traktowaniu
  • Duplikowanie kodu do renderowania treści na serwerze i kliencie

.

Izomorficzny

Co jeśli użyjemy Node JS jako naszego serwera, abyśmy mogli uruchomić ten sam kod JS na obu końcach? Teraz wszystkie nasze trasy są zdefiniowane w jednej konfiguracji routera reagującego i nie musimy powielać naszego kodu renderowania. To jest „święty Graal”, że tak powiem. Serwer wysyła dokładnie takie same znaczniki, jak w przypadku, gdyby nastąpiło przeniesienie strony na kliencie. To rozwiązanie jest optymalne pod względem SEO.

Wady :

  • Serwer musi (być w stanie) uruchomić JS. Eksperymentowałem z Java icw Nashorn, ale to nie działa dla mnie. W praktyce oznacza to głównie, że musisz użyć serwera opartego na Node JS.
  • Wiele trudnych problemów środowiskowych (używanie windowpo stronie serwera itp.)
  • Stroma krzywa uczenia się

.

Z którego powinienem korzystać?

Wybierz ten, z którego możesz uciec. Osobiście uważam, że catch-all jest wystarczająco prosty do skonfigurowania, więc byłoby to moje minimum. Ta konfiguracja pozwala poprawić rzeczy w miarę upływu czasu. Jeśli używasz już Node JS jako platformy serwerowej, zdecydowanie zbadałbym aplikację izomorficzną. Tak, na początku jest ciężko, ale kiedy już to zrozumiesz, jest to bardzo eleganckie rozwiązanie problemu.

Zasadniczo dla mnie byłby to decydujący czynnik. Gdyby mój serwer działał na Node JS, wybrałbym izomorficzny; inaczej wybrałbym rozwiązanie Catch-all i rozwinąłem je (rozwiązanie hybrydowe) w miarę upływu czasu i wymagań SEO.

Jeśli chcesz dowiedzieć się więcej na temat renderowania izomorficznego (zwanego również „uniwersalnym”) za pomocą React, znajdziesz kilka dobrych samouczków na ten temat:

Na początek warto też zapoznać się z niektórymi zestawami startowymi. Wybierz taki, który pasuje do twojego wyboru stosu technologii (pamiętaj, React to tylko V w MVC, potrzebujesz więcej rzeczy, aby zbudować pełną aplikację). Zacznij od obejrzenia tego opublikowanego przez Facebooka:

Lub wybierz jeden z wielu przez społeczność. Jest teraz fajna strona, która próbuje zaindeksować wszystkie:

Zacząłem od tych:

Obecnie używam domowej wersji uniwersalnego renderingu, która została zainspirowana dwoma powyższymi zestawami startowymi, ale są już nieaktualne.

Powodzenia w twojej misji!


2
Świetny post Stijn! Czy poleciłbyś zestaw startowy do aplikacji Reakcja izomorficzna? Jeśli tak, czy możesz podać przykład tego, który wolisz?
Chris

1
@ Paulos3000 Zależy to od używanego serwera. Zasadniczo określasz trasę /*i sprawiasz, że odpowiada ona za pomocą strony HTML. Trudność polega na tym, aby nie przechwytywać żądań plików .js i .css tą trasą.
Stijn de Witt

2
@ Paulos3000 Zobacz tutaj kilka powiązanych pytań: dla Apache / php , dla Express / js , dla J2E / Java .
Stijn de Witt

1
Cześć, próbuję rozwiązania mieszającego, jednak bez powodzenia. Wystąpił createMemoryHistory is not a functionbłąd. Masz coś przeciwko? stackoverflow.com/questions/45002573/...
Leon Gaban

5
@LeonGaban Wygląda na to, że odkąd napisano tę odpowiedź, React Router zmienił swoją implementację. Mają teraz różne instancje routera dla różnych historii i konfigurują historię w tle. Podstawowe zasady są nadal takie same.
Stijn de Witt

115

Wszystkie odpowiedzi tutaj są niezwykle pomocne, dla mnie działało skonfigurowanie mojego serwera Webpack tak, aby oczekiwał tras.

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

HistoryApiFallback naprawił dla mnie ten problem. Teraz routing działa poprawnie i mogę odświeżyć stronę lub wpisać adres URL bezpośrednio. Nie musisz się martwić o obejście na serwerze węzła. Ta odpowiedź oczywiście działa tylko w przypadku korzystania z pakietu WWW.

EDYCJA: zobacz moją odpowiedź tutaj, aby uzyskać bardziej szczegółowy powód, dla którego jest to konieczne: https://stackoverflow.com/a/37622953/5217568


16
Należy pamiętać, że zespół WebPACK nie zaleca korzystania z serwera dev w produkcji.
Stijn de Witt

5
Dla ogólnych celów programistycznych jest to najlepsze rozwiązanie. historyApiFallbackjest wystarczający. Podobnie jak w przypadku wszystkich innych opcji, można go również ustawić z poziomu interfejsu CLI za pomocą flagi --history-api-fallback.
Marco Lazzeri

2
@Kunok Nie. Jest to szybka poprawka dla programistów, ale nadal będziesz musiał wymyślić coś do produkcji.
Stijn de Witt

contentBase: „: /”, ponieważ dostęp do plików aplikacji można uzyskać z adresu URL
Nezih

85

Możesz zmienić .htaccessplik i wstawić to:

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

Używam react: "^16.12.0"i react-router: "^5.1.2" ta metoda jest Catch-all i jest prawdopodobnie najłatwiejszym sposobem na rozpoczęcie pracy.


3
To działa dobrze! i najłatwiej, jeśli nie chcesz restrukturyzować aplikacji
mhyassin

7
Nie zapomnij RewriteEngine On jako pierwszego wiersza
Kai Qing

3
Uwaga: ta odpowiedź dotyczy konfiguracji na serwerze Apache. To nie jest część aplikacji React.
Colton Hicks,

Nie rozumiem powyższych odpowiedzi. Żaden z nauczycieli nie powiedział, że moje linki w ogóle nie będą działać na żywo. Jednak ten prosty plik htaccess zadziałał. Dzięki
Thomas Williams

Działa to również w przypadku eksportu statycznego next.js. Ponieważ tomcat nie jest podobny do węzła, potrzebujemy tej dodatkowej konfiguracji do statycznego eksportu next.js.
giri-jeedigunta

62

Dla użytkowników React Router V4 :

Jeśli próbujesz rozwiązać ten problem za pomocą techniki Hash History wspomnianej w innych odpowiedziach, zwróć uwagę na to

<Router history={hashHistory} >

nie działa w V4, HashRouterzamiast tego użyj :

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

<HashRouter>
  <App/>
</HashRouter>

Odniesienie: HashRouter


Czy możesz podać jakieś szczegóły, dlaczego HashRouter rozwiązuje ten problem? Podany link nie wyjaśnia mi tego. Czy jest też sposób na ukrycie skrótu na ścieżce? Korzystałem z BrowserRouter, ale napotkałem na ten problem 404 ..
Ian Smith

29

Router można wywołać na dwa różne sposoby, w zależności od tego, czy nawigacja odbywa się na kliencie, czy na serwerze. Masz skonfigurowane do działania po stronie klienta. Kluczowym parametrem jest drugi do metody uruchomienia , lokalizacja.

Gdy używasz komponentu React Router Link, blokuje on nawigację w przeglądarce i wywołuje przejście, aby wykonać nawigację po stronie klienta. Korzystasz z HistoryLocation, więc używa interfejsu API historii HTML5, aby uzupełnić iluzję nawigacji, symulując nowy adres URL na pasku adresu. Jeśli używasz starszych przeglądarek, to nie zadziała. Musisz użyć komponentu HashLocation.

Po odświeżeniu pomijasz cały kod React i React Router. Serwer otrzymuje żądanie /joblisti musi coś zwrócić. Na serwerze musisz przekazać żądaną ścieżkę do runmetody, aby mogła wyświetlić poprawny widok. Możesz użyć tej samej mapy trasy, ale prawdopodobnie będziesz potrzebować innego połączenia z Router.run. Jak zauważa Charles, możesz do tego użyć przepisywania adresów URL. Inną opcją jest użycie serwera node.js do obsługi wszystkich żądań i przekazania wartości ścieżki jako argumentu lokalizacji.

Na przykład w ekspresie może to wyglądać tak:

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 });
    });
});

Zauważ, że ścieżka żądania jest przekazywana run. Aby to zrobić, musisz mieć silnik widoku po stronie serwera, do którego możesz przekazać renderowany kod HTML. Istnieje wiele innych uwag dotyczących używania renderToStringi uruchamiania React na serwerze. Gdy strona zostanie wyrenderowana na serwerze, aplikacja załaduje się na kliencie, wyświetli się ponownie, w razie potrzeby aktualizując renderowany HTML po stronie serwera.


2
przepraszam, czy mógłbyś wyjaśnić następną wypowiedź „kiedy odświeżysz, omijasz cały kod React i React Router”? Dlaczego tak się dzieje?
VB_

Router React jest zwykle używany do obsługi różnych „ścieżek” tylko w przeglądarce. Można to zrobić na dwa typowe sposoby: ścieżkę skrótu starszego stylu i interfejs API nowszej historii. Odświeżenie przeglądarki spowoduje żądanie serwera, które obejdzie kod routera po stronie klienta. Musisz obsłużyć ścieżkę na serwerze (ale tylko jeśli korzystasz z interfejsu API historii). Możesz użyć do tego routera reagującego, ale musisz zrobić coś podobnego do tego, co opisałem powyżej.
Todd

Rozumiem. Ale jak być, jeśli serwer działa w języku innym niż JS (powiedzmy Java)?
VB_

2
przepraszam… masz na myśli, że potrzebujesz serwera ekspresowego do renderowania trasy, która nie jest „rootem”? Po raz pierwszy byłem zaskoczony, że definicja izomorfizmu nie uwzględnia pobierania danych w swojej koncepcji (rzeczy są rozpaczliwie skomplikowane, jeśli chcesz renderować widok Z serwerem danych, chociaż byłaby to oczywista potrzeba SEO - i twierdzi to izomorfizm). Teraz jestem jak „WTF reaguje”, poważnie… Popraw mnie, jeśli się mylę, czy ember / angular / backbone wymaga serwera do renderowania trasy? Naprawdę nie rozumiem tego rozdętego wymogu korzystania z tras
Ben

1
Czy to oznacza, że ​​odświeżanie w React-Router nie działa, jeśli moja aplikacja React nie jest izomorficzna?
Matt

28

W pliku index.html headdodaj następujące dane:

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

Następnie podczas uruchamiania z serwerem dewelopera webpack użyj tego polecenia.

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

--history-api-fallback jest ważną częścią


To działało świetnie! Jakieś linki, aby przeczytać więcej o tym, jak znalazłeś / znalazłeś to rozwiązanie?
justDan

1
Przepraszam bracie. Znalazłem to dzięki sprawdzonej technice prób i błędów.
Efe Ariaroo

Warto wspomnieć, że jeśli używasz href bazowego, który nie jest /wtedy HashRouternie będzie działać (jeśli używasz skrótu routing)
Robbie Averill

Tag <base href = "/"> zrobił to dla mnie. Dzięki za to. Serwer deweloperów Webpack wciąż próbował pobrać pakiet ze ścieżki / <bundle> i nie udał się.
Malisbad

27

Właśnie utworzyłem stronę internetową za pomocą aplikacji create-reag-app i przedstawiłem ten sam problem. Używam BrowserRoutingz react-router-dompaczki. Pracuję na serwerze Nginx i tym, co go rozwiązało, było dodanie do/etc/nginx/yourconfig.conf

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

Co odpowiada dodaniu następujących elementów .htaccessw przypadku uruchamiania Appache

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

To także wydaje się być rozwiązaniem zaproponowanym przez Facebooka i można je znaleźć tutaj


1
Wow, takie proste rozwiązanie dla Nginx; działa jak urok, jako nowy użytkownik nginx. Wystarczy skopiować i wkleić i gotowe. +1 za link do oficjalnej dokumentacji fb. Tak trzymać.
user3773048,

Dla przypomnienia: używam wystąpienia AWS z nginx i aplikacją Angular. To działa.
Diego Sarmiento

ratujesz mnie. próbowałem naprawić swój problem od wczoraj zmęczony kilka rozwiązań, ale nie udało mi się. w końcu pomogłeś mi rozwiązać problem z routerem. bardzo dziękuję bracie
Sharifur Robin

Czy jest coś innego potrzebnego do „zareaguj”: „^ 16.8.6”, „reaguj-router”: „^ 5.0.1”? Próbowałem tych rozwiązań (zarówno nginx, jak i apache) i one nie działają.
Mark A. Tagliaferro

Ponieważ Facebook nie zmienił dokumentacji dotyczącej tego, mogę jedynie założyć, że procedura jest taka sama. Jednak od jakiegoś czasu tego nie próbowałem.
Aidin

17

To może rozwiązać twój problem

Ten sam problem napotkałem również w aplikacji ReactJS w trybie produkcyjnym. Oto 2 rozwiązanie problemu.

1. Zmień historię routingu na „hashHistory” zamiast browserHistory zamiast

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

Teraz skompiluj aplikację za pomocą polecenia

sudo npm run build

Następnie umieść folder kompilacji w folderze var / www /. Teraz aplikacja działa dobrze, dodając tag # w każdym adresie URL. lubić

localhost / # / home localhost / # / aboutus

Rozwiązanie 2: Bez tagu # przy użyciu browserHistory,

Ustaw swoją historię = {browserHistory} w routerze, teraz skompiluj ją, używając sudo npm run build.

Musisz utworzyć plik „conf”, aby rozwiązać stronę 404 nie znaleziono, plik conf powinien wyglądać tak.

otwórz terminal wpisz poniższe polecenia

cd / etc / apache2 / sites-available ls nano sample.conf Dodaj w nim poniższą zawartość.

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

Teraz musisz włączyć plik sample.conf za pomocą następującego polecenia

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

następnie poprosi cię o przeładowanie serwera apache, używając usługi sudo apache2 przeładuj lub uruchom ponownie

następnie otwórz folder localhost / build i dodaj plik .htaccess z zawartością poniżej.

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

Teraz aplikacja działa normalnie.

Uwaga: zmień ip 0.0.0.0 na lokalny adres IP.

W razie jakichkolwiek wątpliwości prosimy o komentarz.

Mam nadzieję, że jest to pomocne dla innych.


Czy pierwsze rozwiązanie zadziała "react-router-dom": "^5.1.2"?, używam<BrowserRouter>
151291,

16

Jeśli hostujesz aplikację reagującą za pośrednictwem AWS Static S3 Hosting i CloudFront

Problem ten pojawił się, gdy CloudFront odpowiedział komunikatem 403 Odmowa dostępu, ponieważ spodziewał się / niektóre / inne / ścieżki istnieć w moim folderze S3, ale ścieżka ta istnieje tylko wewnętrznie w routingu React z routerem reagującym.

Rozwiązaniem było skonfigurowanie reguły dystrybucji stron błędów. Przejdź do ustawień CloudFront i wybierz swoją dystrybucję. Następnie przejdź do zakładki „Strony błędów”. Kliknij „Utwórz niestandardową odpowiedź na błąd” i dodaj wpis dla 403, ponieważ otrzymujemy taki kod błędu. Ustaw ścieżkę strony odpowiedzi na /index.html, a kod stanu na 200. Wynik końcowy zadziwia mnie swoją prostotą. Strona indeksu jest wyświetlana, ale adres URL jest zachowywany w przeglądarce, więc po załadowaniu aplikacji reagującej wykrywa ścieżkę adresu URL i przechodzi do żądanej trasy.

Błąd Strony 403 Reguła


1
Właśnie tego potrzebowałem. Dziękuję Ci.
Jonathan

Więc ... wszystkie adresy URL działają, ale zwracają kod stanu 403? To jest niepoprawne. Oczekiwane kody stanu powinny mieścić się w zakresie 2xx.
Stijn de Witt

@StijndeWitt Nie jestem pewien, czy rozumiesz poprawnie. CloudFront zajmie się wszystkim - klient nie zobaczy żadnych kodów stanu 403. Przekierowanie odbywa się po stronie serwera, renderuje stronę indeksu, utrzymuje adres URL i zapewnia w rezultacie prawidłową trasę.
th3morg

@ th3morg Ach tak, widzę teraz. Tęskniłem za tym, że pozwala ci również wpisać kod stanu odpowiedzi jako 200. Tak więc w zasadzie odwzorowujesz status 403 na status 200 ... Wygląda dobrze ... poza tym, że może otrzymasz 403 z jakiegoś innego powodu z tego powodu nie będziesz w stanie tego zobaczyć.
Stijn de Witt

1
dzięki za to @ th3morg. Czy ta konfiguracja działa również w przypadku ścieżek wielopoziomowych? Działa świetnie dla mnie, jeśli jakakolwiek ścieżka na poziomie głównym, taka jak domena / rejestracja, domena / login, domena / <documentId>, ale nie działa dla ścieżek wielopoziomowych, takich jak domena / dokument / <documentId>.
Pargles


12

Jeśli hostujesz swoją aplikację reagującą na IIS, po prostu dodaj plik web.config zawierający:

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

Dzięki temu serwer IIS zwróci stronę główną do klienta zamiast błędu 404 i nie trzeba używać historii skrótów.


dzięki stary!!!! to jest praca!
Thanh Bao

12

Dodaj to do webpack.config.js:

devServer: {
    historyApiFallback: true
}

To świetnie dla deweloperów, ale nie pomaga w kompilacjach produkcyjnych.
KFunk

11

Stos produkcyjny: React, React Router v4, BrowswerRouter, Express, Nginx

1) User BrowserRouter dla ładnych adresów URL

// app.js

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

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

2) Dodaj index.html do wszystkich nieznanych żądań za pomocą /*

// 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) pakiet webpack z webpack -p

4) uruchom nodemon server.jslubnode server.js

EDYCJA: Możesz pozwolić nginx zająć się tym w bloku serwera i zignorować krok 2:

location / {
    try_files $uri /index.html;
}

ścieżka do niezdefiniowanej
Goutham,

@Goutham Na górze Dodaj plik server.js albo import path from 'pathalboconst path = require('path')
Isaac Pak

krok 2 (catch all with /*) właśnie uratował mi dzień. Dziękuję Ci! :)
Atlas7

To działa, dla osób używających redux i zainteresowanych podłączonym routerem zobacz ten przykład: github.com/supasate/connected-react-router/tree/master/examples/…
cjjenkinson 16.04.18

10

Jeśli korzystasz z aplikacji Create React:

Jest jednak świetny spacer z tym problemem z rozwiązaniami dla wielu głównych platform hostingowych, które można znaleźć TUTAJ na stronie Utwórz aplikację React. Na przykład używam React Router v4 i Netlify dla mojego kodu frontendowego. Wystarczyło dodać 1 plik do mojego folderu publicznego („_redirects”) i jeden wiersz kodu w tym pliku:

/*  /index.html  200

Teraz moja witryna poprawnie renderuje ścieżki takie jak mysite.com/pricing, gdy zostanie wprowadzona do przeglądarki lub gdy ktoś odświeży.


7

Spróbuj dodać plik „.htaccess” do folderu publicznego za pomocą poniższego kodu.

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

RewriteRule ^ /index.html [L]  

Dzięki, to właśnie działało dla mnie na Fedorze 28 z apache. Żadna z powyższych reguł przepisywania ani te na stronie CRA nie działały dla mnie. Dodałem je do konfiguracji wirtualnego hosta zamiast w osobnym pliku .htaccess.
boerre

6

Jeśli masz awarię pliku index.html, upewnij się, że w pliku index.html masz:

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

Może się to różnić w zależności od projektu.


2
Dodaj to do swojego html head: <base href = "/">
Efe Ariaroo

4

Jeśli używasz Firebase, wszystko, co musisz zrobić, to upewnić się, że masz właściwość rewrites w pliku firebase.json w katalogu głównym aplikacji (w sekcji hostingowej).

Na przykład:

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

Mam nadzieję, że to ocali komuś frustrację i zmarnowany czas.

Miłego kodowania ...

Dalsze czytanie na ten temat:

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

Firebase CLI: „Skonfiguruj jako aplikację jednostronicową (przepisz wszystkie adresy URL do /index.html)”


Jesteś legendą
Perniferous,


3

Nie korzystam jeszcze z renderowania po stronie serwera, ale napotkałem ten sam problem co OP, w którym Link wydawał się działać przez większość czasu, ale zawiódł, gdy miałem parametr. Udokumentuję tutaj moje rozwiązanie, aby zobaczyć, czy to komukolwiek pomaga.

Mój główny plik jsx zawiera to:

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

Działa to dobrze dla pierwszego pasującego linku, ale gdy zmienia się: id <Link> wyrażeniach zagnieżdżonych na stronie szczegółów tego modelu, adres URL zmienia się na pasku przeglądarki, ale treść strony początkowo nie zmieniła się, aby odzwierciedlić połączony model.

Problem polegał na tym, że użyłem go props.params.iddo ustawienia modelu componentDidMount. Komponent jest montowany tylko raz, co oznacza, że ​​pierwszy model jest tym, który przykleja się do strony, a kolejne linki zmieniają rekwizyty, ale pozostawiają stronę niezmienioną.

Ustawienie modelu w stanie komponentu zarówno w, jak componentDidMounti w componentWillReceiveProps(gdzie jest oparty na następnych rekwizytach) rozwiązuje problem, a zawartość strony zmienia się, odzwierciedlając pożądany model.


1
Lepiej jest użyć konstruktora komponentów (który również ma dostęp do props) iso, componentDidMountjeśli kiedykolwiek chcesz spróbować renderowania po stronie serwera. Ponieważ componentDidMountjest wywoływany tylko w przeglądarce. Jego celem jest robienie rzeczy z DOM, takich jak dołączanie detektorów zdarzeń do bodyitp., W których nie można tego zrobić render.
Stijn de Witt,

3

Ten temat jest nieco stary i rozwiązany, ale chciałbym zaproponować proste, jasne i lepsze rozwiązanie. Działa, jeśli używasz serwera WWW.

Każdy serwer sieciowy ma możliwość przekierowania użytkownika na stronę błędu w przypadku http 404. Aby rozwiązać ten problem, należy przekierować użytkownika na stronę indeksu.

W przypadku korzystania z serwera bazowego Java (tomcat lub dowolnego serwera aplikacji Java) rozwiązaniem może być:

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>

Przykład:

  • POBIERZ http://example.com/about
  • Serwer WWW wyrzuca HTTP 404, ponieważ ta strona nie istnieje po stronie serwera
  • konfiguracja strony błędu informuje serwer, który odsyła stronę index.jsp z powrotem do użytkownika
  • wtedy JS wykona resztę zadania po stronie clien, ponieważ adres URL po stronie klienta jest nadal http://example.com/about .

To tyle, nie ma już magicznych potrzeb :)


To było niesamowite! Korzystam z serwera Wildfly 10.1 i wprowadziłem tę aktualizację do mojego pliku web.xml, z tą różnicą, że ustawiłem lokalizację na „/”. Stało się tak, ponieważ w moim kodzie reagującym użyłem historii przeglądarki w następujący sposób:const browserHistory = useRouterHistory(createHistory)({ basename: '/<appname>' });
Chuck L

1
Czy jesteś pewien, że nie ma to wpływu na SEO? Dajesz status 404 stronom, które faktycznie istnieją. Użytkownik może nigdy nie zdawać sobie z tego sprawy, ale boty zwracają dużą uwagę, w rzeczywistości tak bardzo, że nie zeskrobują strony.
subharb

3

Jeśli używasz Express lub innego frameworka w backend, możesz dodać podobną konfigurację jak poniżej i sprawdzić publiczną ścieżkę Webpack w konfiguracji, powinna działać dobrze nawet przy przeładowaniu, jeśli używasz BrowserRouter

expressApp.get('/*', (request, response) => {
    response.sendFile(path.join(__dirname, '../public/index.html'));
});

to jest najprostsze rozwiązanie. Zwróć uwagę, że ta trasa powinna iść po innych trasach, ponieważ jest to wszystko
dcsan

3

Naprawianie błędu „nie można uzyskać / adresu URL” przy odświeżaniu lub przy bezpośrednim wywołaniu adresu URL.

Skonfiguruj plik webpack.config.js, aby oczekiwać, że podany link będzie przebiegał w ten sposób.

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

2

Podczas korzystania z .Net Core MVC coś takiego pomogło mi:

    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");
        }
   }

Zasadniczo po stronie MVC, wszystkie niepasujące trasy wpadną Home/Indexzgodnie z opisem w startup.cs. Wewnątrz Indexmożna uzyskać oryginalny adres URL żądania i przekazać go w dowolnym miejscu.

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" });
        });

Jeśli interfejs API jest oddzielony od aplikacji, jak sobie z tym poradzisz?
dayanrr91

@ dayanrr91, jeśli twój projekt ma interfejs WWW, i tak nie potrzebujesz routingu po stronie serwera. A twój router reagujący powinien uważać, że powinien przeczytać adres URL i wykonać odpowiedni routing. Nic nie powinno tego blokować
Elnoor

twój komentarz jest bardzo mile widziany, ale mam pytanie, właśnie dodałem HashRouter, ale teraz, gdy ręcznie wprowadzę dowolny adres URL, przekieruję do mojego komponentu domowego zamiast przekierowania do mojego katalogu głównego, a następnie do komponentu, który próbowałem wchodząc, czy jest na to jakieś obejście?
dayanrr91

@ dayanrr91 nie mam pojęcia, nigdy nie próbowałem hashroutera, ale myślę, że powinno to mieć coś wspólnego z twoim kodem. Sposób działania funkcji hashrouter powinien być podobny do działania przeglądarki. Testowałem też tutaj w tym piaskownicy, działa dobrze codeandbox.io/s/25okp1mny , przetestuj adres URL 25okp1mny.codesandbox.io/#/roster/5
Elnoor

2

Jeśli prowadzisz hosting w IIS; Dodanie tego do mojej konfiguracji sieci rozwiązało mój problem

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

Możesz dokonać podobnej konfiguracji dla dowolnego innego serwera


1

Na wypadek, gdyby ktoś szukał rozwiązania w React JS SPA z Laravelem. Przyjęta odpowiedź jest najlepszym wyjaśnieniem, dlaczego tak się dzieje. Jak już wyjaśniono, musisz skonfigurować zarówno po stronie klienta, jak i serwera. W szablonie bloku dołącz plik js, upewnij się, że używasz go w URL facadeten sposób

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

Na swoich trasach dodaj to do głównego punktu końcowego, w którym znajduje się szablon bloku. Na przykład,

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

Powyżej jest głównym punktem końcowym dla szablonu bloku. Teraz dodaj też opcjonalną trasę,

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

Problem, który się zdarza, polega na tym, że najpierw ładowany jest szablon bloku, a następnie router reagujący. Kiedy ładujesz '/setting-alerts', ładuje html i js. Ale po załadowaniu '/setting-alerts/about'najpierw ładuje się po stronie serwera. Ponieważ po stronie serwera nie ma nic w tej lokalizacji, zwraca nie znaleziono. Gdy masz ten opcjonalny router, ładuje tę samą stronę i reaguje na załadowanie routera, a następnie reagujący moduł ładujący decyduje, który składnik ma zostać wyświetlony. Mam nadzieję że to pomoże.


Czy możliwe jest załadowanie do jednego dokładnego identyfikatora URI na serwer, a następnie serwer przekieruje do React? Na przykład, kiedy ładuję /user/{userID}, muszę tylko zwrócić uniwersalny /userwidok HTML, przynieść identyfikator i wywołać go za pomocą AJAX lub axios. czy jest to możliwe do zrobienia? czy są przyjazne SEO?
Ryuujo

1

Dla tych, którzy używają IIS 10, powinieneś to zrobić, aby to zrobić. Upewnij się, że używasz do tego przeglądarki browserHistory. Jeśli chodzi o odniesienie, dam kod do routingu, ale to nie jest ważne, liczy się następny krok po kodzie poniżej:

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"));

Ponieważ problem polega na tym, że IIS otrzymuje żądanie od przeglądarki klienta, zinterpretuje adres URL tak, jakby prosi o stronę, a następnie zwraca stronę 404, ponieważ nie ma dostępnej strony. Wykonaj następujące czynności:

  1. Otwórz IIS
  2. Rozwiń Serwer, a następnie otwórz folder Witryny
  3. Kliknij stronę / aplikację
  4. Przejdź do stron błędów
  5. Otwórz pozycję statusu błędu 404 na liście
  6. Zamiast opcji „Wstaw zawartość z pliku statycznego do odpowiedzi na błąd” zmień ją na „Wykonaj adres URL w tej witrynie” i dodaj wartość „/” ukośnika do adresu URL.

I teraz będzie działać dobrze.

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Mam nadzieję, że to pomoże. :-)


1

Korzystam z WebPack, miałem ten sam problem Rozwiązanie => W twoim pliku 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();
  });

Dlaczego moja aplikacja nie renderuje się po odświeżeniu?


Zrobiło to dla mnie! Początkowo miałem res.sendFile (path.JOIN (publicPath, „index.html”)); Zmieniłem „dołącz” na „rozwiązany” jak w powyższym przykładzie na: res.sendFile (path.resolve („./ dist”, „index.html”)); Bawiłem się też z __dirname, ale tak naprawdę nie mogłem tego zrozumieć ani uruchomić, więc ręcznie skopiowałem plik „./dist”, ponieważ z tego miejsca jest obsługiwany mój index.html. Zadeklarowałem też tak wcześniej: app.use (express.static ("./ dist"));
logixplayer

1

Używanie HashRouterdziałało również dla mnie z Redux , wystarczy po prostu zastąpić:

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();

do:

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();

0

Miałem ten sam problem i to rozwiązanie działało dla nas ..

Tło:

Hostujemy wiele aplikacji na tym samym serwerze. Gdy odświeżymy serwer nie będzie wiedział, gdzie szukać naszego indeksu w folderze dist dla tej konkretnej aplikacji. Powyższy link przeniesie Cię do tego, co dla nas zadziałało ... Mam nadzieję, że to pomaga, ponieważ spędziliśmy sporo godzin na wypracowaniu rozwiązania dla naszych potrzeb.

Używamy:

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"
}

mój 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]
}

mój 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')
)

moja aplikacja.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!');
});

0

Lubię ten sposób obsługi. Spróbuj dodać: yourSPAPageRoute / * po stronie serwera, aby pozbyć się tego problemu.

Podjąłem to podejście, ponieważ nawet natywny interfejs API historii HTML5 nie obsługuje prawidłowego przekierowania przy odświeżaniu strony (o ile mi wiadomo).

Uwaga: wybrana odpowiedź już rozwiązała ten problem, ale staram się być bardziej szczegółowy.

Droga ekspresowa

Test - API historii Przetestowałem i chciałem się tym podzielić.

Mam nadzieję, że to pomoże.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.