2026.09.02
Web 名刺を作ったら、いちばん使われる場所で動いていなかった
はじめに
紙の名刺の裏面を、そのまま Web ページに起こした。URL を 1 本渡せば自己紹介が終わるようにしたかったからで、電話帳に登録できる vCard も一緒に置いた。
作り終えて実機で触ったら、いちばん使われるはずの場所で動いていなかった。LINE で URL を送って、相手が LINE の中で開く。この経路でだけ「連絡先を保存する」が無言で何も起きない。エラーも出ない。押しても何も変わらないので、壊れていることにすら気づきにくい。
Web 名刺そのものより、この手の「特定の環境でだけ静かに死ぬ」話のほうが役に立つと思うので、そこを中心に書きます。
基礎となる知識
- vCard: 連絡先を 1 ファイルにまとめた標準フォーマット。拡張子は
.vcf。スマホで開くと「新規連絡先を作成」の画面が出る - Content-Type: サーバがファイルを返すときに付ける「これは何のファイルか」という札。ブラウザはこれを見て開き方を決める
- アプリ内ブラウザ: LINE や X などのアプリの中で開く簡易ブラウザ。Chrome や Safari とは別物で、できることが制限されている
- コントラスト比: 文字色と背景色の明るさの差。WCAG(Web アクセシビリティの国際的な指針)の AA 基準は 4.5:1
解説と使い方
共通レイアウトを使わないという判断
最初に決めたのは、サイトの共通レイアウトを使わないことだった。ヘッダー・フッター・画面下に固定されるナビゲーションが必ず描画されるので、名刺として渡した URL が「サイトの中の 1 ページ」に見えてしまう。下端のナビゲーションは連絡ボタンとも場所を取り合う。
名刺は名刺として完結していてほしかったので、このページだけ自己完結の HTML にした。サイト全体の統一を崩す判断なので迷ったけれど、渡した相手が最初に見る 1 画面が名刺に見えるかどうかのほうが大事だと思った。
vCard は手書きの静的ファイルにしない
最初は public/onbit-card.vcf に手で書いたファイルを置いていた。これが品質チェックで引っかかった。電話番号がサイト設定ファイルと二重管理になっていて、片方を差し替えるともう片方に古い番号が残る。しかも残るのは相手の電話帳の中なので、間違いに気づく手段が無い。
生成する形に変えた。Astro なら 1 ファイルで済む。
// src/pages/onbit-card.vcf.ts
export const GET: APIRoute = () =>
new Response(
// vCard は CRLF 改行が仕様 (RFC 6350)
LINES.join("\r\n") + "\r\n",
{ headers: { "Content-Type": "text/vcard; charset=utf-8" } },
);
配信 URL は静的ファイルの頃と同じにしたので、既に配った URL や設定はそのまま生きる。連絡先は連絡先の設定ファイル、氏名と肩書は新しく作ったプロフィールの設定ファイル、と出所を 1 か所ずつに固定した。
改行は CRLF、ふりがなは OS で読む場所が違う
vCard で地味に効く仕様が 2 つあった。
1 つは改行が CRLFだということ(RFC 6350)。LF だけでも読める端末はあるけれど、取り込みが安定しない。
もう 1 つがふりがなで、iOS と Android で読むプロパティが違う。両方書いておく。
X-PHONETIC-LAST-NAME:苗字
X-PHONETIC-FIRST-NAME:名前
SOUND;X-IRMC-N:苗字;名前;;;
住所は入れていない。所在地は請求書や契約書には書くけれど、Web には出さない方針にしているので、そこは揃えた。
Content-Type を上書きする
.vcf に対して静的ホスティングが自動で付ける Content-Type は、古い text/x-vcard になることがある。今の標準は text/vcard なので、配信側で上書きした。Cloudflare Pages なら _headers に書く。
/onbit-card.vcf
Content-Type: text/vcard; charset=utf-8
X-Robots-Tag: noindex
ここは思い込みで進めず、wrangler pages dev でローカルに本番同等のヘッダを再現して、実際に置き換わることを確かめてから進めた。ヘッダは以前に一度、同名のヘッダが結合されてページが真っ白になる事故を起こしているので、確認するようにしている。
LINE のアプリ内ブラウザで無言で死ぬ
そして本題。Android の実機で LINE から開いて「連絡先を保存する」を押したら、何も起きなかった。押した感触もエラーも無い。LINE のアプリ内ブラウザは vCard のようなファイルの導線を扱えず、黙って捨てる。
このページは「LINE で URL を送って、相手が LINE の中で開く」のが本来の使われ方なので、そこで動かないなら機能していないのと同じだった。Chrome で開発している限り 100% 再現しないのも厄介なところ。
解決は 1 行だった。LINE には、リンクを外部ブラウザで開かせるためのクエリパラメータがある。
/onbit-card.vcf?openExternalBrowser=1
これを vCard のリンクにだけ付けた。ページ本体は LINE の中で読んだまま、保存の瞬間だけ Chrome や Safari に出る。通常のブラウザでは無視される単なるクエリなので、副作用も無い。
ページ全体を外部ブラウザに逃がすこともできるけど、それだと相手を LINE から追い出すことになる。壊れている 1 つの導線だけを逃がすのがちょうどいいと思う。
download 属性は付けないほうがいい
「保存」なのだから、と download 属性を付けていた。これは外した。iOS の Safari は download を尊重してファイルとして保存してしまい、「新規連絡先を作成」の画面が出ない。連絡先アプリに渡したいなら、素直にリンクとして開かせるほうがいい。
なおこの挙動は現時点で私の手元の iPhone では未確認で、直したのは仕様上の理屈と Android 側での確認まで。ここは正直に書いておきます。
紙の色をそのまま画面に持ってくると落ちる
紙の名刺で使っている金色(#b08a37)を、そのままクリーム色の背景に載せたらコントラスト比が 3.0:1 しかなかった。AA 基準の 4.5:1 に届かない。
紙は反射で読めるけれど、画面はそうはいかない。文字用の金だけ暗くする(#8a6a25 / 4.74:1)ことにして、罫線や枠や飾りのドットは紙と同じ金のままにした。紙の印象は保ったまま、文字だけ読めるようにする、という分け方になる。透明度 0.55 で薄くしていたラベルも 3.4:1 しかなかったので 0.78 まで戻した。
やってみた結果
https://onbit.jp/card/ として公開して、本番の配信も確認できている(vCard は Content-Type が text/vcard; charset=utf-8 で返っている)。URL を手渡しする相手向けのページなので、検索には出さない設定(noindex)にして、サイトマップからも外した。
作ってみて気づいたのは、名刺の Web 版は「作る」より「渡し方」で決まるということだった。デザインを紙と揃えるところは楽しい作業だけれど、成果が変わったのは ?openExternalBrowser=1 の 1 行のほうだった。
もう 1 つ。動作確認の環境が Chrome だけだと、この不具合は永遠に見つからない。LINE の中で見られるページは思っているより多くて、顧客サイトでも同じことが起きる。実機の LINE で開いてみる、というのを手順に足しました。
紙を刷り直したら Web も直す、という運用の紐付けも文書に書いた。ここは仕組みで縛らないと、確実に片方だけ古くなると思っている。
まとめ
- vCard は生成する。静的ファイルに手で書くと連絡先が二重管理になり、間違いは相手の電話帳の中で腐る
- 改行は CRLF。ふりがなは iOS と Android の両方のプロパティを書く
.vcfの Content-Type はtext/vcardに上書きする。上書きが効いているかはローカルで本番同等の環境を立てて確認する- LINE のアプリ内ブラウザはファイルの導線を黙って捨てる。
?openExternalBrowser=1を壊れている導線にだけ付ける download属性は付けない。連絡先アプリに渡らなくなる- 紙の色をそのまま画面に持ってくるとコントラストが足りない。文字用の色を分ける