从公众号自定义菜单进入H5页面的用户,十有八九会顺手点右上角转发给朋友。这时候如果分享卡片显示的是一串默认的网页title和毫无吸引力的描述,运营效果会大打折扣。要让分享出去的卡片根据页面内容动态变化,比如商品页带上商品名称、活动页带上活动标语,就需要接入微信JS-SDK的分享接口。这篇文章从后端签名到前端配置,完整走一遍流程,并把容易踩的坑都列出来。

一、分享接口的原理与前置条件
先厘清一个概念:自定义菜单本身只是入口,点击菜单跳转到H5页面后,分享行为发生在页面内部,所以分享配置和菜单设置没有直接关系,全部工作都在H5页面侧完成。微信目前推荐使用的是wx.updateAppMessageShareData(分享给朋友)和wx.updateTimelineShareData(分享到朋友圈)这两个接口,早期的onMenuShareAppMessage已经被废弃,新项目不要再用了。
调用这两个接口的前提是页面必须通过JS-SDK的权限验证,也就是wx.config校验。整个链路是:公众号的appSecret换取access_token,再用access_token换取jsapi_ticket,服务端基于ticket、随机字符串、时间戳和当前页面URL计算出签名,前端拿到签名后调用wx.config,验证通过才能设置分享内容。
这里有一个容易被忽略的点:签名所用的URL必须是当前页面的完整URL,包括查询参数和hash之前的部分。如果页面是单页应用且使用了history路由,每次路由变化都要重新签名;如果用的是hash路由,URL中#号后面的部分不参与签名,这一点务必注意,否则会出现偶发性签名校验失败的诡异问题。
二、后端签名服务的实现
签名的第一步是获取access_token和jsapi_ticket。这两个值都有调用频率限制,且有效期为7200秒,必须缓存在服务端,推荐存Redis并设置略短于7200秒的过期时间。绝对不要在每次页面请求时都去调微信接口,很容易触发限流,一旦access_token被频繁刷新,还会导致旧token失效,影响公众号其他功能。
以Node.js为例,签名接口的实现如下:
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const redis = require('./redis-client'); // 封装好的redis客户端
const app = express();
// 获取access_token,带缓存
async function getAccessToken() {
const cached = await redis.get('wx_access_token');
if (cached) return cached;
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${process.env.APPID}&secret=${process.env.APP_SECRET}`;
const { data } = await axios.get(url);
if (!data.access_token) throw new Error('获取access_token失败: ' + JSON.stringify(data));
// 提前200秒过期,避免边界问题
await redis.setex('wx_access_token', 7000, data.access_token);
return data.access_token;
}
// 获取jsapi_ticket,同样带缓存
async function getJsapiTicket() {
const cached = await redis.get('wx_jsapi_ticket');
if (cached) return cached;
const token = await getAccessToken();
const url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=jsapi`;
const { data } = await axios.get(url);
if (data.errcode !== 0) throw new Error('获取ticket失败: ' + JSON.stringify(data));
await redis.setex('wx_jsapi_ticket', 7000, data.ticket);
return data.ticket;
}
// 签名接口:前端传入当前页面URL
app.get('/api/wx-signature', async (req, res) => {
try {
const ticket = await getJsapiTicket();
const nonceStr = Math.random().toString(36).slice(2, 16);
const timestamp = Math.floor(Date.now() / 1000);
const url = req.query.url; // 前端传来的完整页面URL
const rawString = `jsapi_ticket=${ticket}&noncestr=${nonceStr}×tamp=${timestamp}&url=${url}`;
const signature = crypto.createHash('sha1').update(rawString).digest('hex');
res.json({ appId: process.env.APPID, timestamp, nonceStr, signature });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(3000);有几个细节要特别注意。第一,拼接签名字符串时参数名必须全小写,noncestr不能写成nonceStr,这是官方文档的规定,写错了签名必然校验失败。第二,前端传来的URL必须是去除hash部分后的URL,如果页面经过iOS的WebView,iOS对pushState的处理有历史遗留问题,它记住的往往是第一次进入时的URL,所以前端在iOS上应该用location.href.split('#')[0],必要时把入口URL存下来复用。
三、前端配置与动态分享内容的写入
后端准备好后,前端引入微信官方的JS-SDK文件,然后先请求签名,再调用wx.config。分享标题和描述可以在wx.ready回调中动态写入,比如从接口拿到商品数据后再设置。示例代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>商品详情页</title>
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
</head>
<body>
<div id="goods-info">加载中...</div>
<script>
async function initShare() {
// 当前页面URL,去掉hash部分
const pageUrl = location.href.split('#')[0];
// 请求后端签名
const resp = await fetch('/api/wx-signature?url=' + encodeURIComponent(pageUrl));
const sign = await resp.json();
wx.config({
debug: false,
appId: sign.appId,
timestamp: sign.timestamp,
nonceStr: sign.nonceStr,
signature: sign.signature,
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData'
]
});
wx.error(function (res) {
console.error('config校验失败', res);
});
wx.ready(function () {
// 这里模拟从接口获取动态数据,实际项目中替换为真实请求
const goods = { title: '限时特惠:机械键盘爆款直降200元',
desc: '仅剩3天,手慢无',
img: 'https://www.bbccb.com/share-cover.jpg' };
// 分享给朋友
wx.updateAppMessageShareData({
title: goods.title,
desc: goods.desc,
link: pageUrl,
imgUrl: goods.img,
success: function () { /* 设置成功 */ }
});
// 分享到朋友圈,朋友圈不显示desc
wx.updateTimelineShareData({
title: goods.title,
link: pageUrl,
imgUrl: goods.img,
success: function () { /* 设置成功 */ }
});
});
}
initShare();
</script>
</body>
</html>分享卡片中的imgUrl建议使用正方形图片,尺寸不小于300x300像素,且必须是可以通过公网访问的HTTPS地址。很多团队发现图片不显示,排查半天才发现图片在测试环境内网或者用了HTTP协议,微信对此要求很严格。另外link参数是别人点开卡片后跳转的地址,可以和当前页面不同,常用于加上渠道追踪参数。
四、单页应用与常见问题排查
如果H5是基于Vue或React的单页应用,分享配置要跟着路由变化。使用hash路由的项目相对省心,因为hash变化不触发页面刷新,签名URL保持不变,只需在路由钩子里重新调用updateAppMessageShareData写入新内容即可。而history路由的项目每次跳转URL都变了,最稳妥的做法是在路由切换时重新向后端请求签名并重新执行wx.config,虽然多一次请求,但能保证签名和URL始终匹配。
排查分享问题时,优先把wx.config的debug设为true,手机上会弹出校验结果。如果提示config:invalid signature,基本是签名URL和实际URL不一致,重点检查iOS的URL快照问题;如果提示invalid url domain,说明公众号后台的JS接口安全域名没有配置当前域名,去公众平台设置里添加即可,注意域名不需要加http协议头,每月修改次数有限制,填写前确认清楚。
还有两个高频坑值得一提。一是苹果手机上分享给朋友时卡片标题显示不对,多半是签名时机太早,页面URL还没最终确定,把签名逻辑放到DOMContentLoaded之后执行更保险。二是分享出去的链接在安卓上正常、iOS上提示页面不存在,通常是link参数带了特殊字符没有encodeURIComponent,两端解析行为不一致导致的。遇到问题不要盲目改代码,先用debug模式收集报错信息,再对照官方文档的签名校验工具逐项核对,效率会高很多。
微信公众号开发JS-SDK分享配置自定义菜单修改时间:2026-09-16 05:57:37