☰
防红跳转怎么做?双HTML方案拆分跳转链路,隐藏真实域名
2026/9/29 17:59:29 网站建设 项目流程

简介:面向网页跳转与防红场景的zip压缩包,内含两个可编辑的HTML模板,用于规避第三方平台对推广链接的限制,将用户从受限环境引导至目标浏览器。两个页面可分别承担入口页与中转逻辑,压缩包整体仅3KB,结构非常精简;用户拿到源码后可直接修改跳转地址、检测规则和延时参数,适合具备前端基础、需要快速部署域名防红页面的营销人员或开发者。已有一千五百二十五人学习下载,说明此类跳转方案在推广场景中有稳定的需求量。代码内部通常借助JavaScript环境判断、Meta Refresh自动刷新、隐藏IFrame以及URL编码解码等手段实现防红跳转,可适配移动端访问场景,帮助链接在不同平台之间保持有效性,尤其适用于微信、QQ等第三方环境内的安全跳转需求。使用时需留意源码授权要求,尊重原创者权益,并建议结合服务端逻辑做安全加固与稳定性校验,避免因规则变动导致失效。

1. 防红跳转到底防的是什么:先想清楚需求再写代码

在微信或 QQ 里分享网页链接,最怕遇到的就是“已停止访问该网页”,或者公众号后台弹一句“菜单跳转链接url可能存在安全风险,请检查”。这种被平台拦下来的状态,从业者习惯叫“红”。域名防红做的不是把不安全的内容伪装成安全,而是把一次跳转链路拆开,让被分享出去的那个 HTML 页面里不出现真实目标地址。常见做法就是准备两个 HTML 代码:第一个做入口过渡页,只负责展示“页面升级访问紧急跳转中”并携带参数跳到第二个页面;第二个页面读取参数后执行最终跳转。适合频繁对外分享活动页、落地页和游戏资讯链接的站点维护者。动手写代码前,先想清楚三件事:目标地址是什么、入口域名有几个、被封后能不能快速换。这三件事想明白,后面就不会反复改模板。

2. 为什么必须是两个 HTML:跳转链路与信任边界

平台检测一个链接是否安全,常见的动作是把分享出去的 URL 抓回来,解析 HTML 源码,看有没有命中风险特征。如果你的页面源码里直接写着一个完整的目标域名,抓取一次就能建立关联。防红的第一原则因此很简单:分享出去的页面里,不出现真实目标地址。

但用户最终又确实要跳到目标地址,这就需要一个中间人。两个 HTML 的链路是:入口页负责隐藏,落地页负责执行。入口页里只有一段固定逻辑和落地页地址,真实目标地址只以 URL 参数的形式存在,而且这个参数是在用户点击之后才由浏览器带出去的,爬虫抓静态源码时看不到目标域名。

2.1 单页 HTML 跳转的最大问题:真实域名藏不住

如果偷懒只写一个 HTML,无论用<meta http-equiv="refresh">还是 JS 的location.href,页面里必然出现类似https://目标域名/xxx这样的完整字符串。从用户角度看,功能没问题;从检测方角度看,目标域名直接暴露在源码里,抓一次就能顺藤摸瓜。更麻烦的是,很多平台对“无交互自动跳转”有单独检测规则,瞬时跳转比手动点击更容易被判定为恶意行为。

单页方案不是完全不能用,它适合那种域名多、红了就扔、不怕暴露的场景。但如果你希望一个落地页能稳定用上一段时间,单页就是把所有鸡蛋放在同一个篮子里。入口域名红了,目标域名也跟着被关联,损失面很大。两页方案的主要价值,就是把这个关联拆开。

2.2 入口页与落地页的分工:谁负责隐藏,谁负责跳转

两页方案把职责拆得很清楚:

  • 入口页:被分享出去的链接,页面文案可以写“页面升级访问每日正常更新”“正在跳转”这类通用提示。它的全部工作就是读取 URL 参数里的target和t,把参数原样拼到落地页地址上,然后跳到落地页。
  • 落地页:放在另一个路径或另一个域名下,读取拿到手的target参数,做合法性校验后执行真正的跳转。

整个链路是这样的:

用户点开分享出来的入口页链接 -> 入口页读取 target/t 参数 -> 浏览器跳转到落地页并携带参数 -> 落地页拼接并校验目标地址 -> 跳转到真实目标

有人会问:落地页为什么不能和入口页放在同一个域名下?如果同域,平台一旦检测到这个域名下的某个页面有问题,很容易顺着目录扫描把落地页也找出来。常见做法是落地页放另一个域名,或者至少放在一个不对外宣传的随机路径下面,比如/x7k2.html。防红是概率问题,不是绝对问题,能减少曝光面就是有效设计。

2.3 三种“跳转执行方式”的取舍:meta refresh、JS 与 302

方式源码暴露情况浏览器行为适用场景
meta refresh目标地址直接写在 HTML 源码里有延时,按回退会回到本页最不推荐,几乎没有防红效果
JSlocation.replace目标地址通过参数拼接,源码里不直接出现无历史记录,回退不回到本页纯静态托管下最主流的方案
服务端 302源码里完全没有目标地址地址栏直接变成目标域名有 Nginx 或后端权限时的进阶方案

JSlocation.replace之所以是首选,是因为它不依赖后端,放在任何对象存储、虚拟主机或静态托管上都能跑。跳转前还能做参数校验,比如只允许http/https协议,拦截javascript:伪协议。服务端 302 是最干净的方案,但需要你有服务器配置权限,对纯静态托管用户来说门槛偏高。

我一般用 JSreplace做主跳转,同时在落地页挂一个“手动打开”的兜底按钮。这样既避免了自动跳转被拦截时用户完全没办法,又不会在页面源码里留下目标地址。下面这章给可直接复制修改的模板。

3. 两个 HTML 的可复用模板:代码、参数约定与替换清单

这一章直接给代码。两个文件都是纯静态 HTML,不依赖任何框架,复制到本地改一行配置就能用。

3.1 入口页 index.html:负责页面展示与参数校验

入口页的核心是LANDING_PAGE这个变量,部署时把它改成落地页的完整地址。真实的目标域名不会出现在这个文件里,只会出现在用户分享时的 URL 参数中。

<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>访问升级中</title> <style> body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: #f2f3f5; margin: 0; } .wrap { max-width: 440px; margin: 72px auto; background: #fff; border-radius: 12px; padding: 30px 22px; text-align: center; box-shadow: 0 4px 18px rgba(0,0,0,.06); } .btn { display: inline-block; margin-top: 18px; background: #3478f6; color: #fff; border-radius: 6px; padding: 9px 26px; text-decoration: none; } </style> </head> <body> <div class="wrap"> <h3>页面升级访问紧急跳转中</h3> <p id="tip">正在前往目标页面,请稍候…</p> <a class="btn" id="go" href="javascript:void(0)">如果未自动跳转,请点击这里</a> </div> <script> (function () { // ===== 部署时只需要改这一行 ===== var LANDING_PAGE = 'https://landing.example.com/go.html'; // ================================= var params = new URLSearchParams(location.search); var target = params.get('target') || params.get('url') || ''; var delay = parseFloat(params.get('t') || '1'); // 参数为空时给出提示,不往下跳 if (!target) { document.getElementById('tip').textContent = '链接缺少跳转参数,请检查原链接'; return; } // target 会拼接进落地页地址,入口页源码里没有真实目标地址 var landing = LANDING_PAGE + '?target=' + encodeURIComponent(target) + '&t=' + delay; document.getElementById('go').onclick = function () { location.replace(landing); }; setTimeout(function () { location.replace(landing); }, delay * 1000); })(); </script> </body> </html>

逻辑说明:页面加载后先解析当前 URL 参数,target是真实目标地址,t是跳转延时秒数。如果用户分享时写的是https://entry.com/index.html?target=https%3A%2F%2Fwww.example.com&t=1,页面会先展示过渡标题,1 秒后跳转到落地页,并带着相同的 target 参数。这里用encodeURIComponent对目标地址编码一次,是为了防止目标地址里的特殊字符把落地页的 URL 结构破坏掉。

注意一个细节:location.replace和location.href的区别在于,replace不会在浏览器历史记录里留下当前页。用户跳转后按回退键,不会回到这个入口页再触发一次跳转,能避免“回退死循环”的体验问题。

3.2 落地页 go.html:负责读取参数并触发跳转

落地页是真正执行跳转的文件。它不负责展示品牌信息,只负责把target参数变成一次浏览器跳转。

<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="robots" content="noindex, nofollow"> <title>跳转中</title> </head> <body> <p id="tip">正在打开目标页面…</p> <script> (function () { var params = new URLSearchParams(location.search); var target = params.get('target') || ''; var delay = parseFloat(params.get('t') || '0'); // 基础校验:只允许 http/https,防止 javascript: 伪协议 if (!target || !/^https?:\/\//i.test(target)) { document.getElementById('tip').textContent = '目标地址不合法,已停止跳转'; return; } function doJump() { // 使用 replace,不产生历史记录,回退键不会回到本页 location.replace(target); } // 延时为 0 时立即跳转;大于 0 时延迟执行 if (delay > 0) { setTimeout(doJump, delay * 1000); } else { doJump(); } // 兜底按钮:自动跳转被浏览器拦截时,用户仍可手动打开 var btn = document.createElement('a'); btn.textContent = '手动打开目标页面'; btn.href = target; btn.style.cssText = 'display:inline-block;margin-top:14px;padding:8px 18px;border:1px solid #3478f6;border-radius:5px;color:#3478f6;text-decoration:none;'; document.body.appendChild(btn); })(); </script> </body> </html>

逻辑说明:落地页从 URL 参数里再次读取target,校验协议头必须是http或https。这个校验很有必要,因为如果有人构造一个javascript:alert(1)之类的链接,落地页不加判断就会被当成开放跳板。校验通过后,按t参数决定是立即跳还是延时跳。

文本框上面的<meta name="robots" content="noindex, nofollow">是告诉搜索引擎不要收录这个页面,降低被爬虫建立索引的概率。手动兜底按钮用 JS 动态创建,而不是写死在 HTML 里,是因为这样源码里就看不到完整目标地址,等用户真正能看到这个按钮时,跳转已经发生或即将发生。

3.3 参数约定一览:target、t 与文件替换清单

两个页面之间的参数约定必须一致,否则链路会断。下面是这套模板约定的参数表:

参数入口页行为落地页行为示例
target读取并原样传给落地页读取并校验后作为跳转目标target=https%3A%2F%2Fwww.example.com
url作为target的别名读取不读取url=https%3A%2F%2Fwww.example.com
t读取并传给落地页读取并控制跳转延时t=1表示延时 1 秒

部署更换文件时,需要替换的内容只有三处:入口页里的LANDING_PAGE变量、入口页和落地页的title文案、以及分享链接中拼接的target参数。文件命名建议全部用小写英文和数字,不要出现空格和中文,避免服务器字符集不一致导致 404。

4. 部署到服务器与验证链路:目录、权限与三处检查

模板写好了,怎么放、怎么验证,是很多人容易忽略的部分。放错目录、权限不对、验证方式错,都会让前面的工作白做。

4.1 两个文件分别放在哪个域名、哪个目录

入口页index.html放在分享出去的入口域名根目录,比如https://entry.com/index.html。落地页go.html放在另一个域名的根目录,比如https://landing.example.com/go.html。

如果只有一个域名,也有变通做法:把go.html改名成一串随机字符,比如/to-8f3k2.html,并且不要把这个路径放在站点导航或 sitemap 里。同时加上 robots.txt 规则:

User-agent: * Disallow: /to-8f3k2.html

上传文件后,确保文件权限是 644、目录权限是 755,否则会出现“页面能打开但资源加载失败”的情况。常见做法是用命令统一处理:

chmod 644 index.html go.html chmod 755 /var/www/html

路径权限不对时,浏览器地址栏能打开首页,但 JS 文件或子路径会返回 403,跳转链路就断在中间。

4.2 验证跳转链路的三条命令与预期结果

部署完先别急着发链接,先用命令行做三处检查。

# 1. 抓取入口页源码,确认里面不包含真实目标地址 curl -s "https://entry.com/index.html?target=https%3A%2F%2Fwww.example.com" | grep -o 'https\?://[^"]*' # 2. 模拟爬虫 UA 抓入口页,看页面是否正常返回 curl -s -A "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" \ "https://entry.com/index.html?target=https%3A%2F%2Fwww.example.com" | head -n 20 # 3. 请求落地页,确认目标参数能正常传递 curl -s "https://landing.example.com/go.html?target=https%3A%2F%2Fwww.example.com&t=0" | grep -o 'target=[^"]*'

第一条命令的关键是:最终输出里只能有落地页的域名landing.example.com,不能出现www.example.com。如果出现了,说明入口页模板里写死了真实地址,需要立刻改掉。第二条命令是模拟搜索引擎的爬虫 UA 访问入口页,确认页面返回 200,而不是平台风控返回的拦截页。第三条命令是核对落地页收到的参数,如果是空输出,说明落地页访问路径不对,或者服务器做了参数过滤。

注意一个容易误判的点:用curl -I访问落地页时,得到的是 200 而不是 302。这是因为落地页是 JS 跳转,服务端只负责返回 HTML,真正跳转发生在浏览器里。看到 200 是正常的,不要以为是部署失败。

4.3 外部访问效果:地址栏与回退键的行为

部署正确后,用户从分享链接点开,整个过程大概是这样:

阶段地址栏显示用户感知
打开分享链接https://entry.com/index.html?target=...看到“页面升级访问紧急跳转中”过渡页
入口页执行跳转https://landing.example.com/go.html?target=...页面短暂白屏或快速切换
落地页执行跳转https://www.example.com/看到目标页面

地址栏最终显示目标地址,这一步前端无法隐藏。用户如果在跳转完成后复制地址栏内容,复制到的就是真实目标地址,二次分享时等于把目标域名直接暴露出去。所以落地页跳转完成后,不要提供任何“复制链接”的按钮,反而应该在入口页放一行提示:请转发原始链接。这个细节下面第 5 章会展开说。

5. 防红跳转的 5 个避坑点:从拦截到参数丢失的排查记录

这套方案看起来简单,真正用起来翻车的地方不少。以下 5 条都是实际部署里反复踩过的坑,按“现象 → 原因 → 解决”写清楚。

5.1 分享链接在聊天软件里直接提示“已停止访问该网页”

现象:用户点开你分享的入口页,还没看到过渡页,直接被平台安全提示页拦下。

原因:链接被人工打开之前,平台已经预先抓取过一次入口页。抓取时命中了几个常见问题:页面标题含诱导词、目标地址出现在源码里、入口域名曾被标记过、或者检测到页面存在“无交互自动跳转”脚本。

解决:先自查,用第 4 章的 curl 命令抓一遍入口页源码,确认没有目标域名。然后检查页面文案,去掉“红包”“抽奖”“加微信”这类高风险词。再把自动跳转延时从 0.8 秒改到 1.5 秒以上,或者在入口页加一个手动点击按钮,让用户主动触发跳转。如果域名本身已经被标记过,任何修改都救不回来,只能换新入口域名。

5.2 跳转后地址栏出现真实域名,用户复制二次分享又把新域名打红

现象:落地页跳转完成后,地址栏显示的是真实目标地址。用户把这个地址复制发到群里,新域名又被提示安全风险。

原因:这是浏览器工作机制,前端没有任何办法在跨域跳转后把地址栏伪装成其他域名。防红只能延迟暴露,不能完全隐藏。

解决:在入口页显眼位置加一行引导文案:“请转发本条原始链接,不要转发跳转后的地址。”同时把真实目标域名设计成可替换的短链形式,即使暴露,也能在 1 分钟内完成域名切换。另外可以在落地页跳转前加一个中间展示层,显示“请耐心等待页面打开”,减少用户去复制地址栏的冲动。

5.3 在微信内置浏览器里不跳转,PC 上却正常

现象:PC 端的 Chrome 打开入口页能正常跳转,微信内置浏览器打开后一直停在过渡页。

原因:微信内置浏览器的 X5 内核缓存了旧版 JS,导致新代码没生效;或者自动跳转被内置浏览器拦截;还有一个常见因素是落地页域名的 HTTPS 证书不是主域名证书,内置浏览器校验失败后直接停止执行 JS。

解决:分享链接时带上版本参数,例如index.html?target=...&v=20250510,每次更新页面后换一个 v 值,让 URL 不同从而绕过缓存。自动跳转延时尽量调到 1.2 秒以上,给页面脚本足够执行时间。落地页域名必须配置有效证书,且证书链完整,自签名证书在这种场景下基本必挂。

5.4 落地页被搜索引擎收录并直接展示目标地址

现象:搜索落地页的路径,能看到快照,点击快照直接跳到目标地址,目标域名等于从搜索结果里暴露了一次。

原因:落地页缺少noindex标记,且文件名过于简单。爬虫顺着入口页的 JS 参数或历史访问记录找到了落地页路径。

解决:落地页必须加上<meta name="robots" content="noindex, nofollow">。文件名改成随机字符串,或者干脆把落地页放到入口域名之外的一个不常用域名下。robots.txt 里也加上 Disallow 规则。注意 robots.txt 只是搜索引擎的“君子协定”,真正的隔离手段还是不要让这个路径出现在任何外部可见的导航、Sitemap 或历史分享记录中。

5.5 换新入口域名后,所有历史分享链接全部失效

现象:入口域名被标记后换了一个新域名,但旧链接打开全是 404,用户完全找不到新入口。

原因:入口页代码里的LANDING_PAGE写死的是旧落地页地址。链接是旧域名入口页的完整 URL,换域名后旧入口文件不存在,自然打不开。

解决:入口页把落地页地址写成同域相对路径,例如/go.html?target=...,这样换域名时只需要把两个文件一起搬到新域名根目录,新链接的路径结构完全一致。更彻底的方案是引入服务端 302 跳转,把落地页收敛到一个固定接口上,换域名只改一行配置。这个做法下一章展开。

6. 进阶:把落地页收敛成服务端 302,跳转行为不再依赖 JS

如果手头有 Nginx 或后端权限,强烈建议把落地页从 HTML 升级成服务端 302。这样跳转逻辑不再依赖浏览器执行 JS,爬虫抓源码时看不到任何目标地址,落地页的稳定性也会高一个级别。

6.1 用 Nginx 实现一个带白名单的 302 跳转接口

# 放在 server {} 块内,将原来的 go.html 替换成 /go 接口 location = /go { # 没有 target 参数直接 404 if ($arg_target = "") { return 404; } # 只允许跳转到自己的几个域名,防止被别人当开放重定向使用 set $ok 0; if ($arg_target ~* "^https?://(www\.example\.com|m\.example\.com)") { set $ok 1; } if ($ok = 0) { return 404; } return 302 $arg_target; }

这个配置的意思是:访问/go?target=...时,Nginx 先检查 target 参数是否存在,再通过正则校验目标地址是否属于你自己的域名白名单。校验通过就直接返回 302,浏览器地址栏跳到目标地址;不通过就返回 404。使用这套方案后,入口页的LANDING_PAGE直接从https://landing.example.com/go.html改成/go,落地页 HTML 文件甚至可以不再部署。

提示:Nginx 的if指令有一些历史遗留行为,不建议在if里写复杂逻辑或proxy_pass。上面这个场景只有set和return,属于安全用法。如果你后续要加更多判断逻辑,更合适的做法是改用 OpenResty 或后端语言实现。

6.2 通过访问日志观察跳转来源与使用情况

换成 302 之后,Nginx 的 access_log 会记录每次/go请求的完整参数,包括来源 IP、UA 和 target 地址。排查问题时非常有价值。

# 实时查看正在发生的跳转请求 tail -f /var/log/nginx/access.log | grep ' /go '

日志中能看到哪个入口域名还在产生流量、哪些 target 地址被频繁请求。换域名后也可以通过日志确认旧链接是否彻底停用。如果担心日志里记录真实目标地址带来隐私问题,可以关闭该路径的日志记录,但大多数人留着日志反而更方便排查。

6.3 部署完后的验证习惯

我自己的习惯是:每次改完模板,第一件事是用 curl 抓入口页源码做一次 grep,确认里面没有出现目标域名;第二件事是用浏览器无痕窗口实测一次跳转链路,重点按回退键看会不会回到入口页形成死循环。这两步做完,才敢把链接发出去。

有一次就是因为链接里残留了旧域名没有清干净,发出去不到半小时就被提示风险,再排查发现是页面里一行注释写死了旧地址。从那以后,“发链接前先 grep 一遍”就成了固定动作。这套双 HTML 方案本身不复杂,真正决定它稳不稳的,往往是这些细小的检查习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询