Background
I have my project deployed to Github Pages here: https://zeddrix.github.io/jw-guitar-tabs, so I have this in my svelte.config.js file:
kit: {
...
paths: {
base: '/jw-guitar-tabs'
},
appDir: 'internal',
...
}
I have this in my __layout.svelte:
<script lang="ts">
import { base } from '$app/paths';
...
const fetchFromDBAndStore = async (category: SongCategoriesType) => {
const res = await fetch(`${base}/api/categories/original-songs`);
const data = await res.json();
console.log(data);
...other code...
};
...I have my code here that uses this data...
</script>
<Layout><slot /></Layout>
Side note: I put it in this file so that this runs on any page, but I have a code to make sure that this doesn't run if I already have the data. This is not the issue.
This calls on the file: src/routes/api/categories/original-songs.ts:
import fetchSongsDB from '$utils/fetchSongsDB';
export const get = async () => fetchSongsDB('originals');
And this fetchSongsDB function fetches the songs from my database.
Everything is working fine in development mode when I run npm run dev and even in preview mode when I run npm run preview after build, of course, in localhost:3000/jw-guitar-tabs.
Issue
However, on the static github page at https://zeddrix.github.io/jw-guitar-tabs, I get this:
It serves the 404 Github Page as the response. I guess it's because it can't find the src/routes/api/categories/original-songs.ts file. But of course Github will not find this file because the deployed folder to gh-pages is the build folder so I don't have this original file route anymore.
How would I solve this?
Rename original-songs.ts to original-songs.json.ts
Change the
fetch(`${base}/api/categories/original-songs`);
to
fetch(`${base}/api/categories/original-songs.json`);
That allows the adapter-static to generate the json files at build time.
(These are static files, to see changes made in mongodb will require a rebuild & redeploy to GitHub pages)
Sometimes the static adapter doesn't autodetect the endpoint.
You can help the adapter by specifying the url in svelte.config.js
kit: {
prerender: {
entries: [
"*",
"/api/categories/original-songs.json",
Bonus tip: The /favorites url redirects to /favorites/ when refreshing the page, add trailingSlash: "never", to kit to generate favorites.html instead of favorites/index.html and keep the /favorites url on refresh.
I am following https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html
How would I get Ember to serve this url https://www.some-url.com/app-app-site-association
Note, app-app-site-association is a file. Unfortunately, Apple chose not to append an extension to this file.
Ideally, accessing this url should render:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "9F32916B95.foo.bar.baz",
"paths": ["*"]
}
]
}
}
The important think here to understand is that if you want to deliver a static file, may it be your /app-app-site-association or just an image on /something.jpg this has nothing to do with ember.
Ember runs in the browser. So the entire ember routing happens in the browser. Thats why you have to configure your webserver in a way that it serves the index.html file when he does not find a file.
So when you enter example.com/something this will trigger a HTTP GET example.com/something. Your webserver then should check if the file something exists, if yes it should respond with 200 OK and the content of that file.
If if does not find the file something it would normally respond with a 404 NOT FOUND. However when you use an SPA as ember you configure your webserver so it will never respond with 404 NOT FOUND but instead return 200 OK and the body of the index.html. (you can see an example configuration in the ember guides, but this will be different for different webservers)
This then will load your ember app (you've included the .js file in the index.html) and ember will check the current url and start the ember routing. This happens in the browser.
So if something wants to make an HTTP request and get the file app-app-site-association, it does probably not want to get your index.html and then run a browser to let ember do anything. It just wants the file directly from your webserver. So your webserver must directly respond with 200 OK and the content of that file. And it will probably do this as I mentioned above if that file just exists.
Now ember projects have a public folder. If you want to have some files alongside your ember application that should just be served by your webserver this is the right place. Whatever you put in it will just be copied to the dist folder. This means when you then deploy your dist folder you will also deploy the file.
However be careful about your webserver configuration. Because the file has no ending it will probably be served as text/plain. If you want it to be served as application/json you need to configure your webserver accordingly.
How do I set CORS on requests for fonts files (or any other static resource) on the built in ember-cli server?
This is the error message just for reference:
Font from origin 'http://some-domain:4200' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:61277' is therefore not allowed access.
Add the following to ENV in config/environment.js:
module.exports = function(environment) {
contentSecurityPolicyHeader: 'Content-Security-Policy',
contentSecurityPolicy: {
// ... other stuff here
'font-src': "'self' http://some-domain:4200",
},
}
I tried adding the CSP settings but it was unsuccessful for me. I still got CORS errors for font files being referenced from my Ember app's CSS. In another post I saw someone mention ember-cli-cors which I tried and seemed to solve the issue for me. It just adds CORS headers allowing requests from everywhere to all resources which is exactly what I needed to get all resources to load properly in my local dev environment serving the Ember app assets using ember-cli's built in ember serve command to another local development server running a Python app serving my index.html from Redis(ember-cli-deploy style).
I'm in the process of splitting into two different projects an Ember.js app and its Express REST API counterpart. I assumed that things would be cleaner this way.
Until then, my Express app was both serving REST endpoints and all static files like index.html and app.js. But now, ember-cli is in charge of serving the static files and the Express app handles authentication + REST.
The last issue I'm having is that I now have two different ports: ember-cli uses http://localhost:4200 and express uses http://localhost:3333. When I get the session cookie from Express upon authentication, it's never being sent on subsequent request because of same origin policy (see: How do I send an AJAX request on a different port with jQuery?).
Now if I understand correctly I have two solutions:
Setup Express to support JSONP and make sure Ember uses it too
Install a local Nginx or Apache and setup a proxy pass
The first solution is not ok because after deployment both apps will use the same domain/port. The second solution would probably work but it seems a bit ridiculous to ask developers to install a local web server to simply run an app.
I'm sure many of you have encountered that issue before. What would you suggest to make development easy?
Thanks!
Hmm. Seems like I found another solution:
Following instructions found there: http://discuss.emberjs.com/t/ember-data-and-cors/3690/2
export default DS.RESTAdapter.extend({
host: 'http://localhost:3333',
namespace: 'api',
ajax: function(url, method, hash) {
hash = hash || {}; // hash may be undefined
hash.crossDomain = true;
hash.xhrFields = { withCredentials: true };
return this._super(url, method, hash);
})
});
You will also need to add the following headers in the Express app:
// Add support for cross-origin resource sharing (localhost only)
app.all('*', function(req, res, next) {
if (app.get('env') === 'development') {
res.header('Access-Control-Allow-Origin', 'http://localhost:4200');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
}
next();
});
That's it! Last step is to make sure that Ember uses CORS only in the dev environment.
UPDATE
Ember-cli now has an integrated proxy feature that makes all the above obsolete.
From documentation: "Use --proxy flag to proxy all ajax requests to the given address. For example ember server --proxy http://127.0.0.1:8080 will proxy all your apps XHR to your server running at port 8080."
I am using S3 to host a javascript app that will use HTML5 pushStates. The problem is if the user bookmarks any of the URLs, it will not resolve to anything. What I need is the ability to take all url requests and serve up the root index.html in my S3 bucket, rather than just doing a full redirect. Then my javascript application could parse the URL and serve the proper page.
Is there any way to tell S3 to serve the index.html for all URL requests instead of doing redirects? This would be similar to setting up apache to handle all incoming requests by serving up a single index.html as in this example: https://stackoverflow.com/a/10647521/1762614. I would really like to avoid running a web server just to handle these routes. Doing everything from S3 is very appealing.
It's very easy to solve it without url hacks, with CloudFront help.
Create S3 bucket, for example: react
Create CloudFront distributions with these settings:
Default Root Object: index.html
Origin Domain Name: S3 bucket domain, for example: react.s3.amazonaws.com
Go to Error Pages tab, click on Create Custom Error Response:
HTTP Error Code: 403: Forbidden (404: Not Found, in case of S3 Static Website)
Customize Error Response: Yes
Response Page Path: /index.html
HTTP Response Code: 200: OK
Click on Create
The way I was able to get this to work is as follows:
In the Edit Redirection Rules section of the S3 Console for your domain, add the following rules:
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<HostName>yourdomainname.com</HostName>
<ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
This will redirect all paths that result in a 404 not found to your root domain with a hash-bang version of the path. So http://yourdomainname.com/posts will redirect to http://yourdomainname.com/#!/posts provided there is no file at /posts.
To use HTML5 pushStates however, we need to take this request and manually establish the proper pushState based on the hash-bang path. So add this to the top of your index.html file:
<script>
history.pushState({}, "entry page", location.hash.substring(1));
</script>
This grabs the hash and turns it into an HTML5 pushState. From this point on you can use pushStates to have non-hash-bang paths in your app.
There are few problems with the S3/Redirect based approach mentioned by others.
Mutliple redirects happen as your app's paths are resolved. For example: www.myapp.com/path/for/test gets redirected as www.myapp.com/#/path/for/test
There is a flicker in the url bar as the '#' comes and goes due the action of your SPA framework.
The seo is impacted because - 'Hey! Its google forcing his hand on redirects'
Safari support for your app goes for a toss.
The solution is:
Make sure you have the index route configured for your website. Mostly it is index.html
Remove routing rules from S3 configurations
Put a Cloudfront in front of your S3 bucket.
Configure error page rules for your Cloudfront instance. In the error rules specify:
Http error code: 404 (and 403 or other errors as per need)
Error Caching Minimum TTL (seconds) : 0
Customize response: Yes
Response Page Path : /index.html
HTTP Response Code: 200
For SEO needs + making sure your index.html does not cache, do the following:
Configure an EC2 instance and setup an nginx server.
Assign a public ip to your EC2 instance.
Create an ELB that has the EC2 instance you created as an instance
You should be able to assign the ELB to your DNS.
Now, configure your nginx server to do the following things: Proxy_pass all requests to your CDN (for index.html only, serve other assets directly from your cloudfront) and for search bots, redirect traffic as stipulated by services like Prerender.io
I can help in more details with respect to nginx setup, just leave a note. Have learnt it the hard way.
Once the cloud front distribution update. Invalidate your cloudfront cache once to be in the pristine mode.
Hit the url in the browser and all should be good.
It's tangential, but here's a tip for those using Rackt's React Router library with (HTML5) browser history who want to host on S3.
Suppose a user visits /foo/bear at your S3-hosted static web site. Given David's earlier suggestion, redirect rules will send them to /#/foo/bear. If your application's built using browser history, this won't do much good. However your application is loaded at this point and it can now manipulate history.
Including Rackt history in our project (see also Using Custom Histories from the React Router project), you can add a listener that's aware of hash history paths and replace the path as appropriate, as illustrated in this example:
import ReactDOM from 'react-dom';
/* Application-specific details. */
const route = {};
import { Router, useRouterHistory } from 'react-router';
import { createHistory } from 'history';
const history = useRouterHistory(createHistory)();
history.listen(function (location) {
const path = (/#(\/.*)$/.exec(location.hash) || [])[1];
if (path) history.replace(path);
});
ReactDOM.render(
<Router history={history} routes={route}/>,
document.body.appendChild(document.createElement('div'))
);
To recap:
David's S3 redirect rule will direct /foo/bear to /#/foo/bear.
Your application will load.
The history listener will detect the #/foo/bear history notation.
And replace history with the correct path.
Link tags will work as expected, as will all other browser history functions. The only downside I've noticed is the interstitial redirect that occurs on initial request.
This was inspired by a solution for AngularJS, and I suspect could be easily adapted to any application.
I see 4 solutions to this problem. The first 3 were already covered in answers and the last one is my contribution.
Set the error document to index.html.
Problem: the response body will be correct, but the status code will be 404, which hurts SEO.
Set the redirection rules.
Problem: URL polluted with #! and page flashes when loaded.
Configure CloudFront.
Problem: all pages will return 404 from origin, so you need to chose if you won't cache anything (TTL 0 as suggested) or if you will cache and have issues when updating the site.
Prerender all pages.
Problem: additional work to prerender pages, specially when the pages changes frequently. For example, a news website.
My suggestion is to use option 4. If you prerender all pages, there will be no 404 errors for expected pages. The page will load fine and the framework will take control and act normally as a SPA. You can also set the error document to display a generic error.html page and a redirection rule to redirect 404 errors to a 404.html page (without the hashbang).
Regarding 403 Forbidden errors, I don't let them happen at all. In my application, I consider that all files within the host bucket are public and I set this with the everyone option with the read permission. If your site have pages that are private, letting the user to see the HTML layout should not be an issue. What you need to protect is the data and this is done in the backend.
Also, if you have private assets, like user photos, you can save them in another bucket. Because private assets need the same care as data and can't be compared to the asset files that are used to host the app.
The easiest solution to make Angular 2+ application served from Amazon S3 and direct URLs working is to specify index.html both as Index and Error documents in S3 bucket configuration.
You can now do this with Lambda#Edge to rewrite the paths
Here is a working lambda#Edge function:
Create a new Lambda function, but use one of the pre-existing Blueprints instead of a blank function.
Search for “cloudfront” and select cloudfront-response-generation from the search results.
After creating the function, replace the content with the below. I also had to change the node runtime to 10.x because cloudfront didn't support node 12 at the time of writing.
'use strict';
exports.handler = (event, context, callback) => {
// Extract the request from the CloudFront event that is sent to Lambda#Edge
var request = event.Records[0].cf.request;
// Extract the URI from the request
var olduri = request.uri;
// Match any '/' that occurs at the end of a URI. Replace it with a default index
var newuri = olduri.replace(/\/$/, '\/index.html');
// Log the URI as received by CloudFront and the new URI to be used to fetch from origin
console.log("Old URI: " + olduri);
console.log("New URI: " + newuri);
// Replace the received URI with the URI that includes the index page
request.uri = newuri;
return callback(null, request);
};
In your cloudfront behaviors, you'll edit them to add a call to that lambda function on "Viewer Request"
Full Tutorial: https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/
I ran into the same problem today but the solution of #Mark-Nutter was incomplete to remove the hashbang from my angularjs application.
In fact you have to go to Edit Permissions, click on Add more permissions and then add the right List on your bucket to everyone. With this configuration, AWS S3 will now, be able to return 404 error and then the redirection rule will properly catch the case.
Just like this :
And then you can go to Edit Redirection Rules and add this rule :
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<HostName>subdomain.domain.fr</HostName>
<ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
Here you can replace the HostName subdomain.domain.fr with your domain and the KeyPrefix #!/ if you don't use the hashbang method for SEO purpose.
Of course, all of this will only work if you have already have setup html5mode in your angular application.
$locationProvider.html5Mode(true).hashPrefix('!');
Was looking for the same kind of problem.
I ended up using a mix of the suggested solutions described above.
First, I have an s3 bucket with multiple folders, each folder represents a react/redux website.
I also use cloudfront for cache invalidation.
So I had to use Routing Rules for supporting 404 and redirect them to an hash config:
<RoutingRules>
<RoutingRule>
<Condition>
<KeyPrefixEquals>website1/</KeyPrefixEquals>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>my.host.com</HostName>
<ReplaceKeyPrefixWith>website1#</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
<RoutingRule>
<Condition>
<KeyPrefixEquals>website2/</KeyPrefixEquals>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>my.host.com</HostName>
<ReplaceKeyPrefixWith>website2#</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
<RoutingRule>
<Condition>
<KeyPrefixEquals>website3/</KeyPrefixEquals>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>my.host.com</HostName>
<ReplaceKeyPrefixWith>website3#</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
In my js code, I needed to handle it with a baseName config for react-router.
First of all, make sure your dependencies are interoperable, I had installed history==4.0.0 wich was incompatible with react-router==3.0.1.
My dependencies are:
"history": "3.2.0",
"react": "15.4.1",
"react-redux": "4.4.6",
"react-router": "3.0.1",
"react-router-redux": "4.0.7",
I've created a history.js file for loading history:
import {useRouterHistory} from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
export const browserHistory = useRouterHistory(createBrowserHistory)({
basename: '/website1/',
});
browserHistory.listen((location) => {
const path = (/#(.*)$/.exec(location.hash) || [])[1];
if (path) {
browserHistory.replace(path);
}
});
export default browserHistory;
This piece of code allow to handle the 404 sent by the sever with an hash, and replace them in history for loading our routes.
You can now use this file for configuring your store ans your Root file.
import {routerMiddleware} from 'react-router-redux';
import {applyMiddleware, compose} from 'redux';
import rootSaga from '../sagas';
import rootReducer from '../reducers';
import {createInjectSagasStore, sagaMiddleware} from './redux-sagas-injector';
import {browserHistory} from '../history';
export default function configureStore(initialState) {
const enhancers = [
applyMiddleware(
sagaMiddleware,
routerMiddleware(browserHistory),
)];
return createInjectSagasStore(rootReducer, rootSaga, initialState, compose(...enhancers));
}
import React, {PropTypes} from 'react';
import {Provider} from 'react-redux';
import {Router} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import variables from '!!sass-variable-loader!../../../css/variables/variables.prod.scss';
import routesFactory from '../routes';
import {browserHistory} from '../history';
const muiTheme = getMuiTheme({
palette: {
primary1Color: variables.baseColor,
},
});
const Root = ({store}) => {
const history = syncHistoryWithStore(browserHistory, store);
const routes = routesFactory(store);
return (
<Provider {...{store}}>
<MuiThemeProvider muiTheme={muiTheme}>
<Router {...{history, routes}} />
</MuiThemeProvider>
</Provider>
);
};
Root.propTypes = {
store: PropTypes.shape({}).isRequired,
};
export default Root;
Hope it helps.
You'll notice with this configuration I use redux injector and an homebrew sagas injector for loading javascript asynchrounously via routing.
Don't mind with theses lines.
Similar to another answer using Lambda#Edge, you can use Cloudfront Functions (which as of August 2021 are part of the free tier).
My URL structure is like this:
example.com - React SPA hosted on S3
example.com/blog - Blog hosted on an EC2
Since I'm hosting the blog on the same domain as the SPA, I couldn't use the suggested answer of having Cloudfront 403/404 error pages using a default error page.
My setup for Cloudfront is:
Set two origins (S3, and my EC2 instance via an Elastic ALB)
Set up two behaviors:
/blog
default (*)
Create the Cloudfront Function
Setup the Cloudfront function as the "Viewer request" of the default (*) behavior
I'm using this Cloudfront function based on the AWS example. This may not work for all cases, but my URL structure for the React app doesn't contain any ..
function handler(event) {
var request = event.request;
var uri = request.uri;
// If the request is not for an asset (js, png, etc), render the index.html
if (!uri.includes('.')) {
request.uri = '/index.html';
}
return request;
}
Edit June 2022
I've updated my function to be this:
function handler(event) {
var request = event.request;
var uri = request.uri;
// Check whether the URI is missing a file name.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Check whether the URI is missing a file extension.
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
Go to your bucket's Static website hosting setting
and set Error document to index.html.
since the problem is still there I though I throw in another solution.
My case was that I wanted to auto deploy all pull requests to s3 for testing before merge making them accessible on [mydomain]/pull-requests/[pr number]/
(ex. www.example.com/pull-requests/822/)
To the best of my knowledge non of s3 rules scenarios would allow to have multiple projects in one bucket using html5 routing so while above most voted suggestion works for a project in root folder, it doesn't for multiple projects in own subfolders.
So I pointed my domain to my server where following nginx config did the job
location /pull-requests/ {
try_files $uri #get_files;
}
location #get_files {
rewrite ^\/pull-requests\/(.*) /$1 break;
proxy_pass http://<your-amazon-bucket-url>;
proxy_intercept_errors on;
recursive_error_pages on;
error_page 404 = #get_routes;
}
location #get_routes {
rewrite ^\/(\w+)\/(.+) /$1/ break;
proxy_pass http://<your-amazon-bucket-url>;
proxy_intercept_errors on;
recursive_error_pages on;
error_page 404 = #not_found;
}
location #not_found {
return 404;
}
it tries to get the file and if not found assumes it is html5 route and tries that. If you have a 404 angular page for not found routes you will never get to #not_found and get you angular 404 page returned instead of not found files, which could be fixed with some if rule in #get_routes or something.
I have to say I don't feel too comfortable in area of nginx config and using regex for that matter, I got this working with some trial and error so while this works I am sure there is room for improvement and please do share your thoughts.
Note: remove s3 redirection rules if you had them in S3 config.
and btw works in Safari
The problem with most of the proposed solutions, especially the most popular answer, is that you never get a 404 error for non-existent backend resources. If you want to continue getting 404, refer to this solution
I added a root path to all my routes (that's new to this solution)
e.g. all my route-paths start with the same front end root...
in place of paths /foo/baz, /foo2/baz I now have /root/foo/baz , /root/foo2/baz paths.
The choice of the front-end root is such that it does not conflict with any real folder or path at the back-end.
Now I can use this root to create simple redirection rules anywhere, I like. All my redirection changes will be impacting only this path and everything else will be as earlier.
This is the redirection rule I added in my s3 bucket
[
{
"Condition": {
"HttpErrorCodeReturnedEquals": "404",
"KeyPrefixEquals": "root/"
},
"Redirect": {
"HostName": "mydomain.com",
"ReplaceKeyPrefixWith": "#/"
}
}
]
Or Even following
[
{
"Condition": {
"KeyPrefixEquals": "root/"
},
"Redirect": {
"HostName": "mydomain.com",
"ReplaceKeyPrefixWith": "#/"
}
}
]
After this, /root/foo/baz is redirected to /#/foo/baz, and the page loads at home without error.
I added the following code on-load after my Vue app is mounted. It will take my app to the desired route. You can change router.push part as per Angular or whatever you are using.
if(location.href.includes("#"))
{
let currentRoute = location.href.split("#")[1];
router.push({ path: `/root${currentRoute}` })
}
Even if you do not use redirection at the s3 level, having a single base to all routes can be handy in whatever other redirections you may prefer. It helps the backend to identify that is not a request for a real back-end resource, the front-end app will be able to handle it.
If you landed here looking for solution that works with React Router and AWS Amplify Console - you already know that you can't use CloudFront redirection rules directly since Amplify Console does not expose CloudFront Distribution for the app.
Solution, however, is very simple - you just need to add a redirect/rewrite rule in Amplify Console like this:
See the following links for more info (and copy-friendly rule from the screenshot):
https://docs.aws.amazon.com/amplify/latest/userguide/redirects.html#redirects-for-single-page-web-apps-spa
https://github.com/aws-amplify/amplify-console/issues/83
A solution not mentioned here is to use CloudFront Functions to rewrite the request URI to index.html on viewer request:
function handler(event) {
var request = event.request;
request.uri = '/index.html';
return request;
}
I was looking for an answer to this myself. S3 appears to only support redirects, you can't just rewrite the URL and silently return a different resource. I'm considering using my build script to simply make copies of my index.html in all of the required path locations. Maybe that will work for you too.
In 2022, the Lambda Function is
function handler(event) {
var request = event.request;
var uri = request.uri;
if (uri.endsWith("/")) {
request.uri += "index.html"
} else if (!uri.includes(".")){
request.uri += "/index.html"
}
return request;
}
Just to put the extremely simple answer. Just use the hash location strategy for the router if you are hosting on S3.
export const AppRoutingModule: ModuleWithProviders = RouterModule.forRoot(routes, { useHash: true, scrollPositionRestoration: 'enabled' });