URL-адреси маршрутизатора React не працюють під час оновлення або запису вручну


653

Я використовую React-роутер, і він прекрасно працює, коли натискаю на кнопки посилань, але коли я оновлюю свою веб-сторінку, вона не завантажує те, що я хочу.

Наприклад, я перебуваю, localhost/joblistі все добре, тому що я приїхав сюди, натискаючи посилання. Але якщо я оновлю веб-сторінку, я отримую:

Cannot GET /joblist

За замовчуванням це не працювало так. Спочатку у мене був свій URL , як localhost/#/і localhost/#/joblistвони працювали прекрасно. Але мені не подобається така URL-адреса, тому намагаючись стерти це #, я написав:

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

З цією проблемою не буває localhost/, ця завжди повертає те, що я хочу.

EDIT: Цей додаток є односторінковим, тому /joblistне потрібно нічого просити на будь-який сервер.

EDIT2: Мій весь маршрутизатор.

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

якщо ви не використовуєте htaccess для завантаження вашої основної сторінки для гастролей і не скажете маршрутизатору використовувати location.pathname, це не працюватиме ..
Чарльз Джон Томпсон III,

Як ви стирали цей #символ? Дякую!
SudoPlz

4
Якщо ви розміщуєте свою програму реагування у відрі S3, ви можете просто встановити документ про помилку index.html. Це дозволить впевнитись, що index.htmlце потрапило незалежно від того.
Хатто Тревор

У моєму випадку він відмінно працює у Windows, але не в Linux
Ejaz Karim

Це посилання, яке допомогло вирішити мою проблему: github.com/facebook/create-react-app/blob/master/packages/…
jimbotron

Відповіді:


1094

Переглядаючи коментарі до прийнятої відповіді та загальний характер цього питання ("не працюю"), я подумав, що це може бути хорошим місцем для деяких загальних роз'яснень щодо проблем, які тут пов'язані. Отже, ця відповідь призначена як довідкова інформація / розробка конкретного випадку використання ОП. Будь ласка, нехай зі мною.

Сторона сервера проти клієнта

Перше велике, що потрібно зрозуміти з цього приводу, це те, що зараз є 2 місця, де інтерпретується URL, тоді як раніше було «1». У минулому, коли життя було простим, якийсь користувач надсилав запит наhttp://example.com/about на сервер, який перевіряв частину шляху URL-адреси, визначав, що користувач запитує сторінку про сторінку, а потім повертає її назад.

З маршрутизацією на стороні клієнта, що і надає React-Router, все є менш простим. Спочатку клієнт ще не завантажив код JS. Тож найперший запит завжди буде до сервера. Після цього повернеться сторінка, що містить необхідні теги скриптів для завантаження React і React Router тощо. Тільки тоді, коли ці сценарії завантажуються, починається етап 2. На другому етапі, коли користувач, наприклад, натискає на навігаційне посилання "Про нас", URL-адреса локально змінюється на http://example.com/about(що стає можливим за допомогою API історії) ), але запит на сервер не робиться. Натомість маршрутизатор React робить свою справу на стороні клієнта, визначає, який перегляд React відобразити, і відобразить його. Якщо припустити, що на вашій сторінці про інформацію не потрібно телефонувати REST, це вже зроблено. Ви перейшли з дому до нас про нас без жодного запиту сервера.

Отже, коли ви натискаєте посилання, деякі запуски Javascript, що маніпулює URL-адресою в адресному рядку, не викликаючи оновлення сторінки , що, в свою чергу, змушує React Router здійснити перехід сторінки на стороні клієнта .

Але тепер подумайте, що станеться, якщо скопіювати та вставити URL-адресу в адресний рядок та надіслати електронною поштою другові. Ваш друг ще не завантажив ваш веб-сайт. Іншими словами, вона все ще перебуває у фазі 1 . На її машині поки не працює маршрутизатор React. Тож її браузер зробить серверний запит наhttp://example.com/about .

І ось тут починаються ваші неприємності. До цих пір ви могли втекти, просто розмістивши статичний HTML у веб-корі свого сервера. Але це призведе до 404помилок у всіх інших URL-адресах, коли запитується від сервера . Ці ж URL-адреси добре працюють на стороні клієнта , оскільки там React Router робить маршрутизацію для вас, але вони не працюють на стороні сервера, якщо ви не змусите їх ваш сервер їх зрозуміти.

Поєднання маршрутизації на стороні сервера та клієнта

Якщо ви хочете, щоб http://example.com/aboutURL працював як на сервері, так і на стороні клієнта, вам потрібно встановити маршрути для нього як на сервері, так і на стороні клієнта. Має сенс правильно?

І саме тут починається ваш вибір. Рішення варіюються від повного обходу проблеми, через загальний маршрут, який повертає завантажувальний HTML, до повного ізоморфного підходу, де і сервер, і клієнт виконують один і той же код JS.

.

Обхід проблеми взагалі: Історія хешу

Якщо історія хешу замість історії веб-переглядачів ваша URL-адреса сторінки про сторінку виглядатиме приблизно так: http://example.com/#/about Частина після #символу хеш ( ) не надсилається на сервер. Тож сервер бачить http://example.com/і відправляє сторінку з індексом як очікувалося. React-Router підбере #/aboutчастину і покаже правильну сторінку.

Недоліки :

  • 'потворні' URL-адреси
  • За допомогою цього підходу серверна візуалізація неможлива. Що стосується оптимізації пошукових систем (SEO), ваш веб-сайт складається з однієї сторінки, на якій майже немає вмісту.

.

Ловити все

При такому підході ви використовуєте історію браузера, а просто налаштувати улов-все на сервері , який посилає /*вindex.html , фактично даючи вам багато таку ж ситуацію , як з історією Hash. Однак у вас є чисті URL-адреси, і ви можете вдосконалити цю схему пізніше, не скасовуючи всіх своїх улюблених користувачів.

Недоліки :

  • Складніше налаштувати
  • Все ще немає хорошого SEO

.

Гібридний

У гібридному підході ви розширюєте сценарій загального доступу, додаючи конкретні сценарії для конкретних маршрутів. Ви можете зробити декілька простих скриптів PHP, щоб повернути найважливіші сторінки вашого сайту із вмістом, що міститься, щоб Googlebot міг принаймні побачити, що на вашій сторінці.

Недоліки :

  • Ще складніше налаштувати
  • Тільки гарне SEO для тих маршрутів, яким ви надаєте спеціальне лікування
  • Дублювання коду для візуалізації вмісту на сервері та клієнті

.

Ізоморфний

Що робити, якщо ми використовуємо Node JS як наш сервер, щоб ми могли запустити один і той же JS-код на обох кінцях? Тепер у нас всі маршрути визначені в одній конфігурації маршрутизатора, і нам не потрібно дублювати наш код візуалізації. Це, так би мовити, «святий грааль». Сервер надсилає точно таку ж розмітку, як і в кінцевому підсумку, якби перехід сторінки відбувся у клієнта. Це рішення є оптимальним з точки зору SEO.

Недоліки :

  • Сервер повинен (вміти) запускати JS. Я експериментував з Java icw Nashorn, але це не працює для мене. На практиці це здебільшого означає, що ви повинні використовувати сервер на базі вузла JS.
  • Багато складних екологічних проблем (використання windowна сервері тощо)
  • Крута крива навчання

.

Який я повинен використовувати?

Виберіть той, з яким ви можете піти. Особисто я вважаю, що все-таки досить просто, щоб встановити, що було б моїм мінімумом. Ця настройка дозволяє вдосконалюватись із часом. Якщо ви вже використовуєте Node JS як свою серверну платформу, я безумовно досліджую, як зробити ізоморфний додаток. Так, спочатку важко, але, як тільки ви отримаєте висіти, це насправді дуже елегантне рішення проблеми.

Отже, в основному, для мене це було б вирішальним фактором. Якщо мій сервер працює на Node JS, я б пішов ізоморфним; інакше я б пішов на рішення Catch-all і просто розширив би його (гібридне рішення) у міру просування часу та вимог SEO.

Якщо ви хочете дізнатися більше про ізоморфне (також його "універсальне") візуалізація за допомогою React, є кілька хороших навчальних посібників з цього питання:

Також, щоб розпочати роботу, рекомендую переглянути кілька початкових наборів. Виберіть той варіант, який відповідає вашому вибору для стека технологій (пам’ятайте, React - це лише V в MVC, вам потрібно більше речей, щоб створити повний додаток). Почніть з розгляду того, який опублікував сам Facebook:

Або виберіть одну із багатьох громадою. Зараз є приємний сайт, який намагається проіндексувати їх усіх:

Я почав з таких:

В даний час я використовую домашню версію універсальної візуалізації, яка була натхненна двома наборами для початку, але вони застаріли.

Успіхів у ваших квестах!


2
Чудовий пост Штійн! Чи порекомендуєте ви скористатися стартовим набором для програми Isomorphic react? Якщо так, чи могли б ви навести приклад того, якого ви б хотіли?
Кріс

1
@ Paulos3000 Це залежить від того, який сервер ви використовуєте. В основному ви визначаєте маршрут для/* і даєте йому відповідати за допомогою вашої HTML-сторінки. Тут складне - переконатися, що ви не перехоплюєте запити файлів .js та .css за допомогою цього маршруту.
Штійн де Вітт

2
@ Paulos3000 Ознайомтеся з деякими пов'язаними питаннями: для Apache / php , для Express / js , для J2E / Java .
Штійн де Вітт

1
Привіт, я пробую хеш-рішення, проте не пощастило. createMemoryHistory is not a functionПомилка отримання . Розумний погляд? stackoverflow.com/questions/45002573 / ...
Leon Габала

5
@LeonGaban Здається, оскільки ця відповідь була написана, React Router змінив їхню реалізацію. Тепер вони мають різні екземпляри маршрутизатора для різних історій, і вони здійснюють налаштування історії у фоновому режимі. Основні принципи досі однакові.
Штійн де Віт

115

Тут усі відповіді надзвичайно корисні. Налаштування сервера Webpack розраховував на очікування маршрутів.

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

HistoryApiFallback - це те, що вирішило для мене це питання. Тепер маршрутизація працює правильно, і я можу оновити сторінку або ввести безпосередньо URL-адресу. Не потрібно турбуватися про роботу навколо ваших серверів вузлів. Ця відповідь очевидно працює лише в тому випадку, якщо ви використовуєте веб-пакет.

EDIT: див. Мою відповідь тут для більш детальної причини, чому це необхідно: https://stackoverflow.com/a/37622953/5217568


16
Зауважте, що команда Webpack рекомендує не використовувати сервер розробників у виробництві.
Штійн де Віт

5
Для цілей загального розвитку це найкраще рішення. historyApiFallbackє достатнім. Що стосується всіх інших варіантів, то його також можна встановити з CLI з прапором --history-api-fallback.
Marco Lazzeri

2
@Kunok Це не так. Це швидке виправлення розвитку, але вам все одно доведеться щось розробити для виробництва.
Штійн де Вітт

contentBase: ': /' тому, що до файлів додатків може бути доступ із URL-адреси
Nezih

85

Ви можете змінити .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>

Я використовую, react: "^16.12.0"і react-router: "^5.1.2" цей метод - це Catch-all і, мабуть, найпростіший спосіб для початку роботи.


3
Це прекрасно працює! і найпростіше, якщо ви не хочете реструктурувати свій додаток
mhyassin

7
Не забудьте RewriteEngine On як перший рядок
Кай Цін

3
Зверніть увагу, що ця відповідь стосується конфігурації на сервері Apache. Це не є додатком React.
Колтон Хікс

Я не розумів відповідей вище. Жоден репетитор не сказав, що мої посилання взагалі не працюватимуть. Однак цей простий файл htaccess працював. Спасибі
Томас Вільямс

Це працює і для статичного експорту next.js. Оскільки tomcat не схожий на вузол, нам потрібна ця додаткова конфігурація для статичного експорту next.js.
giri-jeedigunta

62

Для користувачів React Router V4 :

Якщо ви спробуєте вирішити цю проблему методом історії хешу, згаданим в інших відповідях, зверніть увагу на це

<Router history={hashHistory} >

не працює у V4, будь ласка, використовуйте HashRouterзамість цього:

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

<HashRouter>
  <App/>
</HashRouter>

Довідка: HashRouter


Не могли б ви детально розповісти, чому HashRouter виправляє цю проблему? Надане вами посилання не пояснює це для мене. Також, чи є спосіб приховати хеш на шляху? Я використовував BrowserRouter, але зіткнувся з цим питанням 404 ..
Ian Smith

29

Маршрутизатор можна викликати двома різними способами, залежно від того, відбувається навігація на клієнті чи на сервері. У вас це налаштовано для роботи на стороні клієнта. Основний параметр є другим методом запуску - місцеположення.

Коли ви використовуєте компонент React Router Link, він блокує навігацію в браузері та викликає перехідTo, щоб зробити навігацію на стороні клієнта. Ви використовуєте HistoryLocation, тому він використовує API історії HTML5 для завершення ілюзії навігації, імітуючи нову URL-адресу в адресному рядку. Якщо ви використовуєте старі браузери, це не працюватиме. Вам потрібно буде використовувати компонент HashLocation.

Коли ви натиснете оновити, ви обминаєте весь код React and React Router. Сервер отримує запит /joblistі він повинен щось повернути. На сервері потрібно пройти шлях, який було запрошено до runметоду, щоб він надавав правильний вигляд. Ви можете використовувати ту саму карту маршруту, але вам, ймовірно, потрібен інший дзвінок Router.run. Як зазначає Чарльз, ви можете використовувати переписування URL для цього. Інший варіант - використовувати сервер node.js для обробки всіх запитів і передавати значення шляху як аргумент розташування.

Наприклад, у експресі це може виглядати так:

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

Зауважте, що шлях запиту передається run. Для цього вам потрібно мати механізм перегляду на стороні сервера, на який ви можете передати наданий HTML. Існує ряд інших міркувань щодо використання renderToStringта запуску React на сервері. Після того, як сторінка буде надана на сервері, коли ваша програма завантажується у клієнта, вона знову буде відображатися, оновивши HTML, наданий на стороні сервера, за необхідності.


2
вибачте, вибачте, чи можете ви пояснити, будь ласка, наступне твердження "коли ви натиснете оновити, ви обійдете весь код маршрутизатора React and React"? Чому це відбувається?
VB_

Маршрутизатор React зазвичай використовується для обробки різних "доріжок" лише в браузері. Для цього є два типових способи: старший хеш-стиль шляху та новіший API історії. Оновлення браузера зробить запит сервера, який обійде код маршрутизатора на реакцію на стороні клієнта. Вам потрібно буде обробити шлях на сервері (але лише якщо ви використовуєте API історії). Ви можете використовувати для цього роутер, але вам потрібно зробити щось подібне до того, що я описав вище.
Тодд

зрозумів. Але як бути, якщо сервер не є мовою JS (скажімо, Java)?
VB_

2
Вибачте ... ви маєте на увазі, що вам потрібен експрес-сервер для надання маршруту, який не є кореневим? Мене вперше вразило, що визначення ізоморфізму не включає включення даних у свою концепцію (речі відчайдушно складні, якщо ви хочете зробити перегляд із сервером даних, хоча це було б очевидною потребою в SEO - і ізоморфізм стверджує, що це). Тепер я як "реагую на WTF", серйозно ... Виправте мене, якщо я помиляюся, чи потрібна вугільна / кутова / опорна магістраль для сервера, щоб відобразити маршрут? Я справді не розумію цієї роздутої вимоги використовувати маршрути
Бен

1
Чи означає це, що оновлення в реактор-маршрутизаторі не працює, якщо моя реакція на застосування не є ізоморфною?
Мет

28

headДодайте до свого index.html наступне:

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

Потім при запуску з веб-пакетом сервера розробки використовуйте цю команду.

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

--history-api-fallback є важливою частиною


Це спрацювало чудово! Будь-які посилання, щоб прочитати більше про те, як ви отримали / знайшли це рішення?
щойноDan

1
Вибач, брате. Знайшов це через перевірену техніку спроб та помилок.
Ефе Аріару

Варто відзначити , що якщо ви використовуєте базу HREF, що не /то HashRouterне працюватиме (якщо ви використовуєте хеш маршрутизації)
Robbie Averill

Тег <base href = "/"> зробив це для мене. Дякую за це Сервер розробки Webpack постійно намагався схопити пакет із шляху / <bundle> і не вдався.
Малісбад

27

Я використовував додаток create-react для створення веб-сайту, і тут був представлений той самий випуск. я використовуюBrowserRouting з react-router-domпакета. Я працюю на сервері Nginx, і для мене це вирішило, додавши наступне/etc/nginx/yourconfig.conf

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

Що відповідає додаванню наступного до .htaccessвипадку, якщо ви запускаєте Appache

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

Це також здається рішенням, запропонованим самими Facebook, і його можна знайти тут


1
Нічого собі, таке просте рішення для nginx; працює як шарм, як новий користувач nginx. Просто скопіюйте та вставте, і це добре. +1 для посилання на офіційну документацію fb. Шлях.
користувач3773048

Для запису: я використовую екземпляр AWS з nginx та кутовим додатком. Це працює.
Дієго Сармієнто

ти врятуєш мене. Я намагався виправити свою проблему з вчорашнього дня, я втомився пару рішень, але не зміг. нарешті, ви допомогли мені вирішити цю проблему з маршрутизатором. ах спасибі братові
Шаріфур Робін

Чи є щось інше, необхідне для "реагувати": "^ 16.8.6", "реагувати-маршрутизатор": "^ 5.0.1"? Я спробував ці рішення (і nginx, і apache), і вони не працюють.
Марк А. Тальяферро

Оскільки Facebook не змінив свою документацію щодо цього, я можу лише припустити, що процедура така ж. Я ще не намагався цього часу.
Aidin

17

Це може вирішити вашу проблему

Я також зіткнувся з тією ж проблемою у програмі ReactJS у режимі виробництва. Ось 2 рішення проблеми.

1.Змініть історію маршрутів на "hashHistory" замість браузераHistory замість

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

Тепер побудуйте додаток за допомогою команди

sudo npm run build

Потім розмістіть папку збірки у вашій var / www / папці. Тепер додаток працює чудово, додаючи # тег у кожну URL-адресу. подібно до

localhost / # / home localhost / # / aboutus

Рішення 2: Без # тега за допомогою браузераІсторія,

Встановіть у своєму маршрутизаторі історію = {browserHistory}, а тепер будуйте його за допомогою sudo npm run build.

Вам потрібно створити файл "conf", щоб вирішити сторінку 404 не знайдено, конф-файл повинен бути таким.

відкрийте термінал, введіть наведені нижче команди

cd / etc / apache2 / available-ls nano sample.conf Додайте нижче вміст.

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

Тепер вам потрібно ввімкнути файл sample.conf за допомогою наступної команди

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

тоді він попросить вас перезапустити сервер apache, використовуючи службу sudo, щоб перезавантажити або перезапустити службу sudo

потім відкрийте папку localhost / build і додайте .htaccess файл із вмістом нижче.

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

Тепер додаток працює нормально.

Примітка: змініть 0,0,0,0 ip на свою локальну ip адресу.

Якщо є якісь сумніви з цього приводу, сміливо поставте коментар.

Я сподіваюся, що це корисно для інших.


Перше рішення спрацює "react-router-dom": "^5.1.2"?, я використовую<BrowserRouter>
151291

16

Якщо ви розміщуєте додаток для реагування через AWS Static S3 Hosting & CloudFront

Ця проблема представила CloudFront у відповідь на повідомлення 403 Access Denied, оскільки очікувало, що / деякий / інший / шлях існуватиме в моїй папці S3, але цей шлях існує лише внутрішньо в маршрутизації React за допомогою маршрутизатора react.

Рішенням було встановити правило про помилки розповсюдження у дистрибутиві. Перейдіть до налаштувань CloudFront та виберіть розповсюдження. Далі перейдіть на вкладку «Сторінки помилок». Клацніть "Створити власну відповідь на помилку" та додайте запис для 403, оскільки це код статусу помилки, який ми отримуємо. Встановіть Шлях сторінки відгуку до /index.html, а код статусу - 200. Кінцевий результат дивує мене своєю простотою. Сторінка індексу подається, але URL зберігається у веб-переглядачі, тому щойно додаток, що реагує, завантажується, він визначає шлях до URL-адреси та переходить до потрібного маршруту.

Сторінки помилок 403 Правило


1
Це саме те, що мені було потрібно. Дякую.
Джонатан

Отже .. всі ваші URL-адреси працюють, але поверніть код статусу 403? Це не правильно. Очікувані коди статусу повинні знаходитися в діапазоні 2xx.
Штійн де Вітт

@StijndeWitt Я не впевнений, що ти правильно розумієш. CloudFront впорається з усім - клієнт не побачить жодних 403 кодів статусу. Перенаправлення відбувається на стороні сервера, відображає сторінку з покажчиком, підтримує URL та надає правильний маршрут у результаті.
th3morg

@ th3morg Ага так, я бачу зараз. Я пропустив, що він також дозволяє вам заповнити код статусу відповіді як 200. Тож в основному ви зіставляєте статус 403 на статус 200 ... Виглядає чудово ... за винятком того, що, можливо, якщо ви отримаєте 403 внаслідок якоїсь іншої причини ви не зможете побачити це через це.
Штійн де Вітт

1
дякую за це @ th3morg. Чи працює ця установка також для багаторівневих контурів? Для мене це чудово працює, якщо будь-який шлях на кореневому рівні, наприклад, домен / реєстрація, домен / вхід, домен / <documentId>, але не працює для багаторівневих контурів, як домен / документ / <документId>.
Парглес


12

Якщо ви розміщуєте додаток для реагування на IIS, просто додайте файл web.config, що містить:

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

Це дозволить серверу IIS повернути головну сторінку клієнту замість 404 помилок і не потрібно використовувати хеш-історію.


дякую брате !!!! це робота!
Тхань Бао

12

Додайте це до webpack.config.js:

devServer: {
    historyApiFallback: true
}

Це чудово для розробників, але не допомагає у створенні виробництва.
KFunk

11

Виробничий стек: React, React Router v4, BrowswerRouter, Express, Nginx

1) Користувач BrowserRouter для гарних URL-адрес

// app.js

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

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

2) Додайте index.html до всіх невідомих запитів, використовуючи /*

// 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) зв’язати веб-пакет з webpack -p

4) бігати nodemon server.jsабоnode server.js

EDIT: Ви можете дозволити nginx обробляти це в блоці сервера і ігнорувати крок 2:

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

стати не визначеним
Goutham

@Goutham Угорі файлу server.js додайте import path from 'pathабоconst path = require('path')
Ісаак Пак

крок 2 (наздогнати всіх /*) тільки що врятував мій день. Дякую! :)
Atlas7

Це працює, для людей, які використовують redux та пов'язані з підключеним маршрутизатором, див. Приклад: github.com/supasate/connected-react-router/tree/master/examples/…
cjjenkinson

10

Якщо ви використовуєте Create React App:

Хоча ця проблема чудово прогулюється з рішеннями для багатьох основних хостинг-платформ, які ви можете знайти ТУТ на сторінці Create React App. Наприклад, я використовую React Router v4 і Netlify для свого коду фронтенда. Все, що потрібно, - це додавання 1 файлу до моєї загальнодоступної папки ("_redirects") та одного рядка коду у цьому файлі:

/*  /index.html  200

Тепер мій веб-сайт належним чином відображає такі маршрути, як mysite.com/pricing, коли його вводять у браузер або коли хтось натискає оновити.


7

Спробуйте додати файл ".htaccess" всередині загальнодоступної папки із наведеним нижче кодом.

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

RewriteRule ^ /index.html [L]  

Дякую, саме це працювало для мене на Fedora 28 з апашем. Жодне з перерахованих вище правил перезапису, ані правила на сторінці CRA не працювали для мене. Я додав їх у віртуальну програму хоста, а не в окремий файл .htaccess.
boerre

6

Якщо у вас є резервний файл до index.html, переконайтеся, що у файлі index.html ви маєте це:

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

Це може відрізнятися від проекту до проекту.


2
Додайте це до свого HTML head: <base href = "/">
Efe Ariaroo,

4

Якщо ви використовуєте firebase, все, що вам потрібно зробити, це переконатися, що у вашому файлі firebase.json у корені програми (у розділі хостингу) ви перезаписали властивість.

Наприклад:

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

Сподіваюсь, це заощадить когось ще запас розчарування та витраченого часу.

Щасливе кодування ...

Подальше читання з цього питання:

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

CLI Firebase: "Налаштуйте як односторінковий додаток (перепишіть усі URL-адреси на /index.html)"


Ви легенда
Пернистий

4

Я знайшов рішення для свого SPA з маршрутизатором реагування (Apache). Просто додайте .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>

джерело: https://gist.github.com/alexsasharegan/173878f9d67055bfef63449fa7136042


3

Я поки не використовую серверне візуалізацію, але я зіткнувся з тією ж проблемою, що і з ОП, де, здається, Link працював більшу частину часу, але не вдався, коли у мене був параметр. Я тут задокументую своє рішення, щоб побачити, чи допомагає хтось.

Мій головний jsx містить це:

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

Це добре працює для першого відповідного посилання, але коли зміниться: id <Link> виразах, вкладених на сторінці детальної інформації про цю модель, URL-адреса змінюється на панелі браузера, але вміст сторінки спочатку не змінювався, щоб відображати пов'язану модель.

Проблема полягала в тому, що я використав props.params.idдля встановлення моделі componentDidMount. Компонент просто монтується один раз, тому це означає, що перша модель - це та, яка приклеюється на сторінку, а наступні Посилання змінюють реквізит, але залишають сторінку зовнішньою.

Встановлення моделі в стані компонента componentDidMountі в, і в componentWillReceiveProps(де вона базується на наступному реквізиті) вирішує проблему, а зміст сторінки змінюється, щоб відображати бажану модель.


1
Можливо, буде краще використовувати конструктор компонентів (до якого також є доступ props) iso, componentDidMountякщо ви хочете спробувати провести серверне візуалізацію. Тому що componentDidMountвикликається тільки в браузері. Його мета полягає в тому, щоб займатися доступом до дому, наприклад, приєднувати слухачів подій до bodyтощо, до чого ви не можете render.
Штійн де Віт

3

Ця тема трохи давня і вирішена, але я хотів би запропонувати вам просто, зрозуміле та краще рішення. Він працює, якщо ви використовуєте веб-сервер.

Кожен веб-сервер має можливість перенаправляти користувача на сторінку помилок у випадку http 404. Щоб вирішити цю проблему, вам потрібно перенаправити користувача на індексну сторінку.

Якщо ви використовуєте базовий сервер Java (tomcat або будь-який сервер додатків Java), рішенням може бути наступне:

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>

Приклад:

  • Отримайте http://example.com/about
  • Веб-сервер кидає http 404, оскільки ця сторінка не існує на стороні сервера
  • конфігурація сторінки помилок повідомляє серверу, який надсилає сторінку index.jsp назад користувачеві
  • тоді JS зробить решту роботи на стороні клієнта, оскільки URL-адреса на стороні клієнта все ще http://example.com/about .

Це все, більше ніяких магічних потреб :)


Це було приголомшливо! Я використовую сервер Wildfly 10.1 і здійснив це оновлення у своєму файлі web.xml, за винятком того, що для мене місце розташування було встановлено просто '/'. Це було тому, що в коді реагування я використовував історію браузера так:const browserHistory = useRouterHistory(createHistory)({ basename: '/<appname>' });
Чак L

1
Ви впевнені, що це не впливає на SEO? Ви надаєте статус 404 сторінкам, які фактично існують. Користувач може ніколи цього не усвідомлювати, але боти приділяють багато уваги, насправді настільки багато, що вони не зіскоблюють вашу сторінку.
субгарб

3

Якщо ви використовуєте Express або якусь іншу рамку в бекенді, ви можете додати аналогічну конфігурацію, як показано нижче, і перевірити загальнодоступний шлях Webpack у конфігурації, він повинен працювати добре навіть при перезавантаженні, якщо ви використовуєте BrowserRouter

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

це найпростіше рішення. зауважте, що цей маршрут повинен пройти за будь-якими іншими маршрутами, оскільки це
спіймана

3

Виправлення помилки "не можу отримати / URL" під час оновлення або безпосередньо виклику URL-адреси.

Налаштуйте свій webpack.config.js так, щоб очікувати дане посилання на такі маршрути.

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

2

Оскільки я використовую .Net Core MVC, щось подібне мені допомогло:

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

В основному на стороні MVC, всі маршрути, не збігаються, потраплятимуть так, Home/Indexяк зазначено в startup.cs. Всередині Indexможна отримати оригінальну URL-адресу запиту та передати її там, де потрібно.

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

Якщо веб-api відокремлено від програми, як ви з цим впораєтеся?
dayanrr91

@ dayanrr91, якщо у вашого проекту є веб-api, вам не потрібно буде маршрутизація на стороні сервера. І ваш реактор-маршрутизатор, я вважаю, повинен прочитати URL і зробити відповідну маршрутизацію. Ніщо не повинно перекривати це
Elnoor

ваш коментар дуже цінується, але тоді у мене виникло питання, я щойно додав HashRouter, але тепер, коли я ввожу будь-яку URL-адресу вручну, він перенаправить на мій домашній компонент, а не перенаправляє на мій дім, а потім на компонент, на який я намагався в’їжджаючи, чи є для цього вирішення?
dayanrr91

@ dayanrr91 Поняття насправді, ніколи не пробував hashrouter ніколи, але я думаю, має бути щось спільне з вашим кодом. Спосіб роботи хеш-рутера повинен бути схожим на браузерний. Також я щойно тестував тут, у цій пісочниці, він працює чудово codeandbox.io/s/25okp1mny , тестує URL 25okp1mny.codesandbox.io/#/roster/5
Elnoor

2

Якщо ви хостите в IIS; Додавання цього до моєї вебконфігу вирішило мою проблему

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

Ви можете зробити подібну конфігурацію для будь-якого іншого сервера


1

У випадку, якщо хтось шукає рішення щодо SPA React JS SPA з Laravel. Прийнята відповідь - найкраще пояснення того, чому трапляються такі проблеми. Як уже було пояснено, ви повинні налаштувати як клієнтську, так і сторону сервера. У свій шаблон леза включіть файл із js у комплекті, переконайтесь, що він використовується URL facadeтаким чином

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

У своїх маршрутах обов’язково додайте це до основної кінцевої точки, де знаходиться шаблон леза. Наприклад,

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

Наведене вище є основною кінцевою точкою шаблону леза. Тепер також додайте необов'язковий маршрут,

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

Проблема, яка трапляється, полягає в тому, що спочатку завантажується шаблон леза, потім маршрутизатор реагує. Отже, коли ви завантажуєте '/setting-alerts', він завантажує html та js. Але коли ви завантажуєте '/setting-alerts/about', він спочатку завантажується на стороні сервера. Оскільки на стороні сервера немає нічого в цьому місці, він повертається не знайдений. Коли у вас є цей необов'язковий маршрутизатор, він завантажує ту саму сторінку і маршрутизатор реагує також завантажується, а потім завантажувач реагує вирішує, який компонент показати. Сподіваюсь, це допомагає.


Чи можна завантажити один точний URI на сервер, тоді сервер здійснить переадресацію на React? Наприклад, коли я завантажуюсь /user/{userID}, мені просто потрібно повернути універсальний /userперегляд HTML, принести ідентифікатор і викликати його за допомогою AJAX або axios. Чи можна це зробити? вони SEO дружні?
Рюйо

1

Для тих, хто використовує IIS 10, це потрібно зробити, щоб зробити це правильно. Будьте впевнені, що ви використовуєте для цього браузерHistory. Що стосується довідки, то я дам код для маршрутизації, але це не те, що має значення, важливий наступний крок після компонентного коду нижче:

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

Оскільки проблема полягає в тому, що IIS отримує запит від клієнтських браузерів, він буде інтерпретувати URL-адресу, ніби просить про сторінку, а потім повертає сторінку 404, оскільки немає доступної сторінки. Зробіть наступне:

  1. Відкрити IIS
  2. Розгорніть сервер, а потім відкрийте папку Сайти
  3. Клацніть веб-сайт / додаток
  4. Перейдіть на сторінки помилок
  5. Відкрийте елемент списку помилок 404 у списку
  6. Замість параметра "Вставити вміст із статичного файлу у відповідь на помилку" змініть його на "Виконати URL-адресу на цьому веб-сайті" та додайте до URL-адреси значення "/".

І зараз це буде добре працювати.

введіть тут опис зображення введіть тут опис зображення

Я сподіваюся, що це допомагає. :-)


1

Я використовую WebPack, у мене була така ж проблема Рішення => У вашому файлі 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();
  });

Чому моя програма не оновлюється після оновлення?


Це зробив це для мене! Спочатку у мене був res.sendFile (path.JOIN (publicPath, "index.html")); Я змінив "приєднатися" до "вирішений", як у наведеному вище прикладі на: res.sendFile (path.resolve ("./ dist", "index.html")); Я також розігрувався з __dirname, але не міг зрозуміти його чи змусити його працювати, тому я скопіював вручну в "./dist", тому що саме тут подається мій index.html. Я також декларував це так раніше: app.use (express.static ("./ dist"));
logixplayer

1

Використовуючи HashRouterдля мене також Redux , просто замініть:

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

до:

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

У мене була ця сама проблема, і це рішення працювало на нас ..

Фон:

Ми розміщуємо кілька додатків на одному сервері. Коли ми оновимо сервер, не зрозуміємо, де шукати наш індекс у папці dist для цього конкретного додатка. Наведене вище посилання приведе вас до того, що працювало для нас ... Сподіваємось, це допомагає, оскільки ми витратили досить багато годин на пошук рішення для наших потреб.

Ми використовуємо:

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

мій 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]
}

мій 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')
)

мій 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!');
});

0

Мені подобається такий спосіб поводження з ним. Спробуйте додати: yourSPAPageRoute / * на стороні сервера, щоб позбутися цієї проблеми.

Я пішов з таким підходом, тому що навіть рідний API історії HTML5 не підтримує правильне перенаправлення на оновлення сторінки (наскільки я знаю).

Примітка. Вибрана відповідь уже вирішила це питання, але я намагаюся бути більш конкретним.

Експрес-маршрут

Тест - API API Тестував і просто хотів цим поділитися.

Сподіваюся, це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.