我正在使用 S3 托管将使用 HTML5 pushStates 的 javascript 应用程序。问题是如果用户为任何 URL 添加书签,它不会解决任何问题。我需要的是能够接受所有 url 请求并在我的 S3 存储桶中提供根 index.html,而不仅仅是进行完全重定向。然后我的 javascript 应用程序可以解析 URL 并提供正确的页面。
有没有办法告诉 S3 为所有 URL 请求提供 index.html 而不是重定向?这类似于通过提供单个 index.html 来设置 apache 来处理所有传入请求,如本示例中所示:https://stackoverflow.com/a/10647521/1762614。我真的很想避免仅仅为了处理这些路由而运行 Web 服务器。从 S3 做所有事情都非常吸引人。
在 CloudFront 的帮助下,无需 url hack 即可轻松解决。
创建 S3 存储桶,例如:react
使用以下设置创建 CloudFront 分配: 默认根对象:index.html 源域名:S3 存储桶域,例如:react.s3.amazonaws.com
默认根对象:index.html
Origin Domain Name:S3存储桶域,例如:react.s3.amazonaws.com
转到错误页面选项卡,单击创建自定义错误响应:HTTP 错误代码:403:禁止(404:未找到,如果是 S3 静态网站)自定义错误响应:是响应页面路径:/index.html HTTP 响应代码: 200:确定点击创建
HTTP 错误代码:403:禁止(404:未找到,如果是 S3 静态网站)
自定义错误响应:是
响应页面路径:/index.html
HTTP 响应代码:200:确定
点击创建
我能够让它工作的方式如下:
在您的域的 S3 控制台的编辑重定向规则部分,添加以下规则:
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<HostName>yourdomainname.com</HostName>
<ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
这会将导致找不到 404 的所有路径重定向到您的根域,并使用该路径的 hash-bang 版本。因此,如果 /posts 中没有文件,http://yourdomainname.com/posts 将重定向到 http://yourdomainname.com/#!/posts。
然而,要使用 HTML5 pushStates,我们需要接受这个请求并根据 hash-bang 路径手动建立正确的 pushState。因此,将其添加到 index.html 文件的顶部:
<script>
history.pushState({}, "entry page", location.hash.substring(1));
</script>
这会抓取哈希并将其转换为 HTML5 pushState。从此时起,您可以使用 pushStates 在您的应用程序中设置非散列路径。
<script language="javascript"> if (typeof(window.history.pushState) == 'function') { window.history.pushState(null, "Site Name", window.location.hash.substring(2)); } else { window.location.hash = window.location.hash.substring(2); } </script>
<ReplaceKeyPrefixWith>#/</ReplaceKeyPrefixWith>
的解决方案成功地完成了 react-router
其他人提到的基于 S3/Redirect 的方法几乎没有问题。
当您的应用程序的路径被解析时,会发生多次重定向。例如: www.myapp.com/path/for/test 被重定向为 www.myapp.com/#/path/for/test 由于“#”的出现和消失,url 栏中会闪烁你的 SPA 框架。 seo 受到影响是因为 - '嘿!它的谷歌强迫他对你的应用程序的重定向 Safari 支持进行了折腾。
解决方案是:
确保您已为您的网站配置了索引路由。主要是 index.html 从 S3 配置中删除路由规则 在 S3 存储桶前面放置一个 Cloudfront。为您的 Cloudfront 实例配置错误页面规则。在错误规则中指定: Http 错误代码:404(和 403 或其他错误根据需要) 错误缓存最小 TTL(秒):0 自定义响应:是 响应页面路径:/index.html HTTP 响应代码:200 用于 SEO 需要+ 确保您的 index.html 没有缓存,请执行以下操作:配置 EC2 实例并设置 nginx 服务器。为您的 EC2 实例分配一个公共 IP。创建一个将您创建的 EC2 实例作为实例的 ELB 您应该能够将 ELB 分配给您的 DNS。现在,配置您的 nginx 服务器以执行以下操作:代理_将所有请求传递给您的 CDN(仅用于 index.html,直接从您的云端提供其他资产)和搜索机器人,按照 Prerender.io 等服务的规定重定向流量
我可以提供有关 nginx 设置的更多详细信息,只需留言即可。已经学会了它的艰难方式。
一旦云端分发更新。使您的云端缓存失效一次以进入原始模式。在浏览器中点击 url,一切都应该很好。
这是切题的,但对于那些使用具有 (HTML5) 浏览器历史记录的 Rackt's React Router library 并希望在 S3 上托管的人来说,这是一个提示。
假设用户在您的 S3 托管的静态网站上访问 /foo/bear
。给定 David's earlier suggestion,重定向规则会将它们发送到 /#/foo/bear
。如果您的应用程序是使用浏览器历史记录构建的,那么这不会有什么好处。但是此时您的应用程序已加载,它现在可以操作历史记录。
在我们的项目中包含 Rackt history(另请参阅 React Router 项目中的 Using Custom Histories),您可以添加一个了解哈希历史路径的侦听器并根据需要替换路径,如下例所示:
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'))
);
回顾一下:
David 的 S3 重定向规则会将 /foo/bear 定向到 /#/foo/bear。您的应用程序将加载。历史监听器将检测 #/foo/bear 历史记法。并用正确的路径替换历史。
Link
tags 将按预期工作,所有其他浏览器历史记录功能也一样。我注意到的唯一缺点是初始请求时发生的插页式重定向。
这受到 a solution for AngularJS 的启发,我怀疑它可以很容易地适应任何应用程序。
browserHistory.listen
我看到了这个问题的 4 个解决方案。前三个已经包含在答案中,最后一个是我的贡献。
将错误文档设置为 index.html。问题:响应正文将是正确的,但状态码将是 404,这会伤害 SEO。设置重定向规则。问题:URL 被 #!加载时页面闪烁。配置 CloudFront。问题:所有页面都将从源返回 404,因此您需要选择是否不缓存任何内容(建议的 TTL 0)或者是否缓存并在更新站点时遇到问题。预渲染所有页面。问题:预渲染页面的额外工作,特别是当页面频繁更改时。例如,新闻网站。
我的建议是使用选项 4。如果您预呈现所有页面,则预期页面不会出现 404 错误。页面将正常加载,框架将控制并正常充当 SPA。您还可以设置错误文档以显示通用 error.html 页面和重定向规则以将 404 错误重定向到 404.html 页面(没有 hashbang)。
关于 403 Forbidden 错误,我根本不让它们发生。在我的应用程序中,我认为主机存储桶中的所有文件都是公共的,我使用具有读取权限的所有人选项来设置它。如果您的站点有私有页面,那么让用户查看 HTML 布局应该不是问题。您需要保护的是数据,这是在后端完成的。
此外,如果您有私人资产,例如用户照片,您可以将它们保存在另一个存储桶中。因为私有资产需要与数据一样的关注,并且无法与用于托管应用程序的资产文件进行比较。
使从 Amazon S3 提供的 Angular 2+ 应用程序和直接 URL 工作的最简单解决方案是在 S3 存储桶配置中将 index.html 指定为索引和错误文档。
https://i.stack.imgur.com/OIGS3.jpg
body
。状态码将是 404,它会伤害 SEO。
body
,如果您在 head
中导入了任何脚本,当您直接点击您网站上的任何子路由时,它们将不起作用
我今天遇到了同样的问题,但是@Mark-Nutter 的解决方案不完整,无法从我的 angularjs 应用程序中删除 hashbang。
实际上,您必须转到编辑权限,单击添加更多权限,然后将您的存储桶上的正确列表添加到每个人。使用此配置,AWS S3 现在将能够返回 404 错误,然后重定向规则将正确捕获该情况。
https://i.stack.imgur.com/VkJVF.png
然后你可以去编辑重定向规则并添加这个规则:
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<HostName>subdomain.domain.fr</HostName>
<ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
如果您不将 hashbang 方法用于 SEO,您可以在此处将 HostName subdomain.domain.fr 替换为您的域和 KeyPrefix #!/。
当然,所有这些只有在你的 Angular 应用程序中已经设置了 html5mode 时才会起作用。
$locationProvider.html5Mode(true).hashPrefix('!');
您现在可以使用 Lambda@Edge 来重写路径
这是一个有效的 lambda@Edge 函数:
创建一个新的 Lambda 函数,但使用预先存在的蓝图之一而不是空白函数。搜索“cloudfront”并从搜索结果中选择 cloudfront-response-generation。创建函数后,将内容替换为以下内容。我还不得不将节点运行时更改为 10.x,因为在撰写本文时 cloudfront 不支持节点 12。
'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);
};
https://i.stack.imgur.com/jTmVE.png
return callback(null, request);
正在寻找同样的问题。我最终混合使用了上述建议的解决方案。
首先,我有一个包含多个文件夹的 s3 存储桶,每个文件夹代表一个 react/redux 网站。我还使用云端缓存失效。
所以我不得不使用路由规则来支持 404 并将它们重定向到哈希配置:
<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>
在我的 js 代码中,我需要使用 react-router 的 baseName
配置来处理它。首先,确保您的依赖项是可互操作的,我安装了与 react-router==3.0.1
不兼容的 history==4.0.0
。
我的依赖是:
“历史”:“3.2.0”,
“反应”:“15.4.1”,
"react-redux": "4.4.6",
“反应路由器”:“3.0.1”,
"react-router-redux": "4.0.7",
我创建了一个用于加载历史记录的 history.js
文件:
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;
这段代码允许使用哈希处理服务器发送的 404,并在历史记录中替换它们以加载我们的路由。
您现在可以使用此文件来配置您的商店和您的根文件。
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;
希望能帮助到你。你会注意到这个配置我使用 redux 注入器和一个自制的 sagas 注入器来通过路由异步加载 javascript。不要介意这些行。
与使用 Lambda@Edge 的另一个 answer 类似,您可以使用 Cloudfront Functions(自 2021 年 8 月起,它已成为 free tier 的一部分)。
我的网址结构是这样的:
example.com - 托管在 S3 上的 React SPA
example.com/blog - 托管在 EC2 上的博客
由于我将博客托管在与 SPA 相同的域中,因此我无法使用使用默认错误页面的 Cloudfront 403/404 错误页面的建议答案。
我对 Cloudfront 的设置是:
设置两个来源(S3 和我的 EC2 实例通过 Elastic ALB) 设置两个行为:/blog 默认 (*) 创建 Cloudfront 函数 将 Cloudfront 函数设置为默认 (*) 行为的“查看器请求”
我正在使用基于 AWS example 的 Cloudfront 函数。这可能不适用于所有情况,但我的 React 应用程序的 URL 结构不包含任何 .
。
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;
}
编辑 2022 年 6 月
我已将我的功能更新为:
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;
}
转到您的存储桶的静态网站托管设置并将错误文档设置为 index.html。
因为问题仍然存在,但我提出了另一个解决方案。我的情况是,我想在合并之前将所有拉取请求自动部署到 s3 进行测试,使它们可以在 [mydomain]/pull-requests/[pr number]/ 上访问(例如 www.example.com/pull-requests/822/ )
据我所知,非 s3 规则场景将允许使用 html5 路由在一个存储桶中拥有多个项目,因此虽然上面大多数投票的建议适用于根文件夹中的项目,但不适用于自己的子文件夹中的多个项目。
所以我将我的域指向我的服务器,在该服务器上遵循 nginx 配置完成了这项工作
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;
}
它尝试获取文件,如果未找到,则假定它是 html5 路由并尝试。如果您有未找到路线的 404 角度页面,您将永远无法到达 @not_found 并返回 404 角度页面而不是未找到文件,这可以通过 @get_routes 中的某些 if 规则或其他内容来修复。
我不得不说我在 nginx 配置和使用正则表达式方面感觉不太舒服,我通过一些试验和错误得到了这个工作,所以虽然这个工作我相信还有改进的空间,请分享你的想法.
注意:如果您在 S3 配置中有 s3 重定向规则,请删除它们。
顺便说一句,在 Safari 中工作
如果您在这里寻找与 React Router 和 AWS Amplify 控制台一起使用的解决方案 - 您已经知道您不能直接使用 CloudFront 重定向规则,因为 Amplify 控制台不会为应用程序公开 CloudFront 分发。
但是,解决方案非常简单 - 您只需在 Amplify 控制台中添加重定向/重写规则,如下所示:
https://i.stack.imgur.com/DuJ0E.png
有关更多信息,请参阅以下链接(以及屏幕截图中的复制友好规则):
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
大多数提议的解决方案的问题,尤其是最流行的答案,是您永远不会收到不存在的后端资源的 404 错误。如果你想继续得到404,参考这个解决方案
我为所有路由添加了根路径(这是此解决方案的新功能),例如,我所有的路由路径都以相同的前端根目录开头...代替路径 /foo/baz、/foo2/baz 我现在有了 /root /foo/baz , /root/foo2/baz 路径。前端根目录的选择不会与后端的任何真实文件夹或路径发生冲突。现在我可以使用这个根在任何地方创建简单的重定向规则,我喜欢。我所有的重定向更改都只会影响这条路径,其他一切都会像以前一样。
这是我在 s3 存储桶中添加的重定向规则
[
{
"Condition": {
"HttpErrorCodeReturnedEquals": "404",
"KeyPrefixEquals": "root/"
},
"Redirect": {
"HostName": "mydomain.com",
"ReplaceKeyPrefixWith": "#/"
}
}
]
甚至跟随
[
{
"Condition": {
"KeyPrefixEquals": "root/"
},
"Redirect": {
"HostName": "mydomain.com",
"ReplaceKeyPrefixWith": "#/"
}
}
]
在此之后,/root/foo/baz 被重定向到 /#/foo/baz,并且页面在首页加载而没有错误。
安装 Vue 应用程序后,我在加载时添加了以下代码。它会将我的应用程序带到所需的路线。您可以根据 Angular 或您使用的任何内容更改 router.push 部分。
if(location.href.includes("#"))
{
let currentRoute = location.href.split("#")[1];
router.push({ path: `/root${currentRoute}` })
}
即使您不在 s3 级别使用重定向,在您可能喜欢的任何其他重定向中,对所有路由都有一个单一的基础也很方便。它帮助后端识别不是对真正后端资源的请求,前端应用程序将能够处理它。
此处未提及的解决方案是使用 CloudFront Functions 将请求 URI 重写为 viewer request 上的 index.html
:
function handler(event) {
var request = event.request;
request.uri = '/index.html';
return request;
}
我自己也在寻找这个问题的答案。 S3 似乎只支持重定向,您不能只重写 URL 并默默地返回不同的资源。我正在考虑使用我的构建脚本在所有必需的路径位置简单地复制我的 index.html。也许这也对你有用。
只是提出一个非常简单的答案。如果您在 S3 上托管,只需对路由器使用哈希定位策略。
export const AppRoutingModule: ModuleWithProviders = RouterModule.forRoot(routes, { useHash: true, scrollPositionRestoration: 'enabled' });
不定期副业成功案例分享
.com/app
(s3) 和.com/auth
(ec2/whatever)。错误响应是顶级的,因此无法从 /auth 或其他任何地方分辨 s3 403 和 403 之间的差异。私有的、仅限预览版的 Lambda@Edge 是解决方案,是 mod_rewrite 的荒谬解决方案。