admin管理员组文章数量:1129457
I deployed my React website build/
folder into an AWS S3 bucket.
If I go to www.mywebsite
, it works and if I click on a
tag to go to Project and About pages, it leads me to the right page.
However, if I copy and send the page url or go straight to the link like: www.mywebsite/projects
, it returns 404.
Here's my App.js
code:
const App = () => (
<Router>
<div>
<NavBar/>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/projects" component={Projects}/>
<Route exact path="/about" component={About}/>
<Route component={NoMatch}/>
</Switch>
</div>
</Router>
);
I deployed my React website build/
folder into an AWS S3 bucket.
If I go to www.mywebsite.com
, it works and if I click on a
tag to go to Project and About pages, it leads me to the right page.
However, if I copy and send the page url or go straight to the link like: www.mywebsite.com/projects
, it returns 404.
Here's my App.js
code:
const App = () => (
<Router>
<div>
<NavBar/>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/projects" component={Projects}/>
<Route exact path="/about" component={About}/>
<Route component={NoMatch}/>
</Switch>
</div>
</Router>
);
Share
Improve this question
edited Jul 7, 2018 at 0:27
Tholle
113k21 gold badges208 silver badges196 bronze badges
asked Jul 7, 2018 at 0:21
VietViet
6,92315 gold badges46 silver badges78 bronze badges
3
|
20 Answers
Reset to default 219I'm not sure if you have already solved it. I had the same issue.
It's because AWS S3 is looking for that folder (projects) to serve from, which you don't have.
Simply point Error document to index.html
.
This worked for me. You can handle error pages in react-router.
Update 1 May 2020:
Since this post is quite active, I need to update the answer:
So you have a few options to solve the issue:
- You can put index.html in the Error document box (like Alan Friedman suggested).
- Go to your bucket (the one that actually has the code - not the one you use to redirect) -> Properties -> Static website hosting:
- This is not "hacky" but it works because of the way
react-router
works: It handles the requests from the front-end and routes users to other routes but overall, the React app is a single-page application. - If you want server-side React, consider using Next.js.
You can put a designated error file error.html in the public folder of your React app and in the Static website hosting: Error document box, put in: error.html. This also works. I've tested it.
Use AWS CloudFront > CloudFront Distributions > The distribution of your bucket > Error Pages and add the error code you want. If you don't use
react-router
(like the example below), the bucket will respond with Error 403 so you can respond with your error.html.
- For
TypeScript
, currentlyreact-router
doesn't support it so you should consider option 2 and 3. They're going to update the library in future version6.*
according to this thread.
In case this S3 bucket is served from a CloudFront distribution:
Create a custom error page (AWS Docs) in CloudFront distribution that routes 404 errors to index.html and returns 200 response code. This way your application will handle the routing.
Custom Error Pages
Redirecting 4xx to index.html would work, but that solution would make you fall into many other problems like google won't be able to crawl these pages. Please check this link, it solved this issue by using redirection rules for the S3 bucket.
There are some good answers in this thread mostly around configuring your S3 bucket with redirection rules. Although this would have also worked for my use case, which is typically hosting a React single page application using React Router through CloudFront with S3 configured as the Origin, I decided to try a different approach given a kind of new feature which was recently released, CloudFront Functions. The main idea is to rewrite the URL before it reaches CloudFront cache so that URLs created by React Router on the client side are rewritten to point to the application domain root.
You can read more about CloudFront Functions here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html
The main idea is to use a CloudFront Function for rewriting the request Uri for any requests that are not targeting static files, i.e .js, .CSS, .html etc... and making it point to the /index.html file. This function would run as part of the viewer request trigger and run before CloudFront checks to see whether the requested object is in the CloudFront cache. That would result in any Uri sent by the client that is generated by the React Router to be rewritten before the content is requested from the origin server, thus not requiring to do any further redirects on S3. At the same time, files that are requested but do not exist anymore on the origin would return a 404 as expected, while all other static content would be served as requested.
Here is how my CloudFront function looks like:
function handler(event) {
var request = event.request;
if (!request.uri.includes('.')) {
request.uri = "/index.html";
request.querystring = {}
}
return request;
}
You can make the URL re-writing rules as complex as you like, but surprisingly, this small piece of code works fine for my use case. Also, unlike Lambda@Edge functions, CloudFront Functions are much more lightweight and allow you to experiment almost real time through AWS console by changing the code of your function there, testing it and deploying it within a couple seconds. However, they come with certain language limitations, so do not expect all JavaScript language features to be supported. This page documents what is available for you to use:
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html
Below configuration resolved
Updated error pages in associated cloud-front distribution with custom error response from http status code 404-not-found to code 200-OK at response page path as / and near zero time-to-live
100% Working and Tested to host SPA website like React in S3 Bucket and fix 404 sub pages issue
- Login to amazon console
- search for Cloud front
- Select your Distributions
- Select Error Pages tab and Click on Create custom Error Response
- Create a response rule here
- Save error rule
You have Done!
This question already has several great answers. Although the question is now old, I faced this problem today, so I feel that perhaps this answer can help.
S3 has two kinds of end points, and if you are facing this error, you have probably selected the S3 bucket directly as the endpoint in the Origin Domain Name field for your CloudFront Distribution.
This would look something like: bucket-name.s3.amazonaws.com
and is a perfectly valid endpoint. In fact, it would seem that AWS does expect this as default behaviour; this entry will be in the drop-down list you have when you are creating your CloudFront distribution.
However, doing this and then setting error pages may or may not solve your problem.
S3 has, however, a dedicated Website Endpoint. This is accessible from your S3 Bucket > Properties > Static Website Hosting. (You will see the link on the top).
You should use this link instead of the original auto-populated link that comes up in CloudFront. You can put this in while creating your distribution, or after creation, you can edit from the tab Origins and Origins Groups, and then invalidating caches.
This link will look like: bucket-name.s3-website.region-name.amazonaws.com
.
Once this propagates and changes, this should then fix your problem.
tl;dr: Don't use the default S3 endpoint in CloudFront. Use the S3 website endpoint instead. Your origin domain should look like this: my-application.s3-website.us-east-1.amazonaws.com
and not like my-application.s3.amazonaws.com
.
More Information about website endpoints is available in the AWS Documentation here.
There are many answers here so I thought I would do a survey of the existing ones to date, and compare pros and cons of the various approaches.
Approach | Description | Pros | Cons | Answers |
---|---|---|---|---|
CloudFront Function | Use an AWS CloudFront function to write requests to the root document of the SPA e.g. index.html |
No error thrown, no redirection, apparently good for SEO. AWS suggest this approach in one of their docs. | Extra cost (although at the time of writing this the free tier allows 2M invocations free per month and after that it's only 10c per million invocations). Extra infrastructure to deploy/manage. | KG |
Host as website | Configure the S3 bucket to host a website, rather than a REST API. | Routing handled automatically by S3 when it's configured this way. | Can't use Orign Access Identity. Rather, the S3 endpoint is exposed over HTTP and you need to restrict access using custom headers, which is more complicated. | FV |
Error Redirection (404 and 403) | The vast majority of answers seem to offer some flavour of this. The idea is to allow the 404 or 403 error to happen, but then return index.html (or whatever the SPA root document is) as the error page. |
Easy to implement. | Causes a redirect for the client every time they try to access a non-root URL. Apparently this can be bad for SEO. | AR, V, MG, PB, K, KS |
URL Hashes / Hashbangs | Some solutions involve routing sub-pages using hashes in the URLs. | No redirection & no cost for extra AWS resources. | There seem to be issues with these approaches - see here. Hashes in URLs are considered ugly by some; there do seem to be ways to mitigate this, but that involves some extra complexity. | AA, B |
Note: I am basing this answer on what I've read in other answers. I have not fully tested any approach except for the "CloudFront Function" approach, which I can confirm works.
Case use Cloudfront to S3:
https://hackernoon.com/hosting-static-react-websites-on-aws-s3-cloudfront-with-ssl-924e5c134455
3b) AWS CloudFront — Error Pages After creating the CloudFront distribution, while its status is In Progress, proceed to the Error Pages tab. Handle response codes 404 and 403 with Customize Error Response.
Google recommends 1 week or 604800 seconds of caching. What we are doing here is to set up CloudFront to handle missing html pages, which typically occurs when a user enters an invalid path or, in particular, when they refresh a path other than the root path.
When that happens:
CloudFront will be looking for a file that does not exist in the S3 bucket; there is only 1 html file in the bucket and that is the index.html for the case of a Single Page Application like this project example A 404 response will be returned and our custom error response setup will hijack it. We will return a 200 response code and the index.html page instead. React router, which will be loaded along with the index.html file, will look at the url and render the correct page instead of the root path. This page will be cache for the duration of the TTL for all requests to the queried path. Why do we need to handle 403 as well? It is because this response code, instead of 404, is returned by Amazon S3 for assets that are not present. For instance, a url of https://yourdomain.com/somewhere will be looking for a file called somewhere (without extension) that does not exist.
PS. It used to be returning 404, but it seems to be returning 403 now; either way it is best to handle both response codes).
This thread has been really helpful for me. I know this is old, but, I'd like to add a aws-cdk
code snippet that finally worked for me. My use case needed me to utilize aws-cdk
, so, I'm adding it here if you're looking forward to a aws-cdk
based solution.
The code snippet adds index.html
as error page in s3 and also in cloudfront. For cloudfront specifically, I added the error page as /index.html
for 404
and 403
with a response of 200
// imports...
// import * as cdk from '@aws-cdk/core';
// import * as s3 from '@aws-cdk/aws-s3';
// import * as cloudfront from '@aws-cdk/aws-cloudfront';
// following code snippet has to be added in a
// class which extends to a Construct or a Stack
const bucket = new s3.Bucket(this, '<Specify a name>', {
bucketName: '<add your bucket name>',
websiteIndexDocument: 'index.html',
websiteErrorDocument: 'index.html',
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.DESTROY, // edit it according to your use case, I'll change it though
autoDeleteObjects: true,
});
const cloudFrontOAI = new cloudfront.OriginAccessIdentity(this, 'OAI');
const distribution = new cloudfront.CloudFrontWebDistribution(this, props?.stackName + 'AbcWebsiteDistribution', {
defaultRootObject: "index.html",
errorConfigurations: [{
errorCode: 404,
responsePagePath: "/index.html",
responseCode: 200
}, {
errorCode: 403,
responsePagePath: "/index.html",
responseCode: 200
}],
originConfigs: [
{
s3OriginSource: {
s3BucketSource: bucket,
originAccessIdentity: cloudFrontOAI,
originPath: "/artifacts" // change this based on your use case
},
behaviors: [{ isDefaultBehavior: true }]
}
]
})
bucket.grantRead(cloudFrontOAI.grantPrincipal);
Answers that really helped me from this thread are:
https://stackoverflow.com/a/56655629/6211961
https://stackoverflow.com/a/58978355/6211961
https://stackoverflow.com/a/64201582/6211961
I'm going to add yet another method, because the CloudFront Function option is great, except that it returns soft-404s, which are bad for your SEO.
This method uses both a CloudFront Function and CloudFront Distribution Custom Error Page to return valid URIs with 200 status codes, and invalid URIs with 404 status codes.
The Function:
This particular function is designed for the routing configuration specified in the React Router v6 Quick Start guide, where /invoices
and /expenses
are nested under the root route /
, plus a catch-all route for 404s *
. It redirects requests to the URIs included in the validPaths
array to /index.html
, allowing other requests through as-is. You should adapt this function to reflect the valid URIs of your SPA, and you may elect to change how this function checks the request.uri
against the validPaths
.
function handler(event) {
var request = event.request;
var validPaths = ['/','/invoices','/expenses'];
if( validPaths.includes(request.uri) ){
request.uri = "/index.html"
}
return request;
}
Any valid static asset requests will still return static assets, but invalid requests to either pages or assets will return an error in XML with a 403 status code.
The Error Handler:
If we combine this with a custom error response that turns 403s into 404s + /index.html, we have an app that will allow React Router to work correctly, without returning soft 404s! Your local search engine crawler will be happy.
CloudFront Distribution Custom Error Response Form image link, because I don't have enough points for it show up inline.
Other Thoughts:
The downside of this approach is that the list of valid URIs in the function must be maintained manually. There are other approaches similar to this that are discussed in this article.
Note that for this approach, you should be using the REST API as your endpoint, with access restricted by an OAI, and not the S3 static website endpoint. This will ensure all traffic to your website is through your CloudFront distribution, protecting private content (if applicable) but also ensuring that your CloudFront logs, if you use them, are complete.
here is my solution without using cloudfront
- setting error page to index.html
- add redirect rule to top when 404 error
I ran into this issue despite using the configuration as described in the accepted answer, but if you still get 403 errors, AND you're using AWS Cloudflare Manager, you can create an error page in the "Error Pages" tab of the distribution settings, with the following configuration:
Error Code: 404,
Customize Error Response: Yes,
Response Page Path: /index.html,
HTTP Response Code: 200
Pic of Error Page Config to pass route handling to browser router
This worked for me although I'm by no means knowledgeable about proper server admin, hopefully someone will tell me if this is a major issue, if serendipitous.
update config of s3 with hosting static and default with index.html enter image description here
add CloudFront with error page
404 error-> index.html->status 200
enter image description here
Why it happens
Your issue is that you want to pass responsibility to routing to your react app/javascript. The link will work because react can listen to the link click and simply update the route in the browser URL bar. However, if you go a location where your script (index.html and the bundle.js or wherever your app code is located) is not loaded, then the JavaScript is never loaded and has no chance to handle the request. Instead, whatever runs your server will take care of the request, look if there is a resource at this place, and return a 404 error or whatever else it found.
The solution
As mentioned in the comments, this is the reason why you need to redirect 404-errors to the exact location where your app is placed. This is nothing specific to Amazon, it is a general requirement to set up your react app.
To solve the issue you need to find out what handles routing on your server and how to configure it. For example if you have an Apache server, an .htaccess file could take care of the matter like this:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
This file lets the server redirect not found errors to the index.html. Keep in mind that it might affect other routing rules that are on your server, so this configuration is easiest, if your react app has a place on its own, not interfering with other stuff.
As per previous answers, the best way to solve the problem: redefine the fallback strategy. If you'd like to learn more about client-side routing vs server-side, and particularly about different routing approaches in the React JS, check out this article.
I fixed it by using HashRouter
instead of Router
import { Switch, Route, HashRouter } from 'react-router-dom';
const App = () => (
<HashRouter>
<div>
<NavBar/>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/projects" component={Projects}/>
<Route exact path="/about" component={About}/>
<Route component={NoMatch}/>
</Switch>
</div>
</HashRouter>
);
The app is acceccible by something like
https://your-domain.s3.amazonaws.com/index.html?someParam=foo&otherPram=bar/#/projects
on localhost like
https://localhost:3000/?someParam=foo&otherPram=bar/#/projects
No changes in S3 were needed.
I'm using NextJS and had the same issue. My solution was to check the routing information and push if it doesn't fit.
const router = useRouter();
useEffect(() => {
if (!router.pathname.startsWith(router.asPath)) {
router.push(router.asPath);
}
});
Nothing needed to be modified at the S3 side except from routing the error page to index.html
In the latest version, Route
is supposed to be the child of Routes
, so we should wrap Route
in Routes
.
<Routes>
<Route path="/home" element={<Home/>}
</Routes>
本文标签: javascriptreact router doesn39t work in aws s3 bucketStack Overflow
版权声明:本文标题:javascript - react router doesn't work in aws s3 bucket - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736719313a1949379.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
index.html
andbundle.js
on any other route than/
. It needs to serve those files on all routes. – Tholle Commented Jul 7, 2018 at 0:26index.html
– Alan Friedman Commented Jul 7, 2018 at 0:34bundle.js
is called explicitly wheneverindex.html
is served. So what you need is to make sure thatindex.html
is served on any path you expect to support deep-linking, and it will callbundle.js
appropriately. If you don't have a 404 file - make index.html your 404. If you do - use an explicit paths list in your cloudfront behaviors. – Radagast the Brown Commented Dec 11, 2023 at 10:23