source.unsplash.com đã biến mất: Khám nghiệm tử thi và mọi cách thay thế
Bị ngừng hỗ trợ vào năm 2021 với lời hứa “các trường hợp sử dụng hiện có sẽ tiếp tục hoạt động”, bị tắt hẳn vào tháng 6/2024, và vẫn đang được viết vào code mới hôm nay. Bản khám nghiệm tử thi khách quan — cùng ba giải pháp thay thế, với proxy giúp khôi phục tính ngẫu nhiên không cần API key.
Một dịch vụ ảnh stock tắt một subdomain, và hai năm sau nó vẫn đang làm hỏng các trang tài liệu, màn hình đăng nhập, bài tập khóa học và cả mã nguồn vừa mới được sinh ra. Đây là bài mổ xẻ về một URL — nó từng làm gì, cái gì đã giết chết nó, và nên thay thế bằng gì — cùng một góc nhìn thận trọng về phần kỳ lạ hơn của câu chuyện: những cỗ máy viết mã cho chúng ta vẫn chưa nhận ra rằng nó đã biến mất.
Các phản hồi HTTP, hai mục changelog được trích dẫn nguyên văn, giới hạn API, các issue tracker của những dự án bị hỏng, cùng số liệu từ GitHub, npm và Stack Overflow — tất cả đều lấy từ nguồn gốc, với phương pháp cho từng mục được ghi trong phần chú thích. Nếu bạn chỉ muốn cách khắc phục, nhảy tới bảng migrate.
Bạn nhận được gì hôm nay, nếu vẫn gọi URL đó
Một lệnh, không cần key, tái tạo được từ bất kỳ máy nào:
curl -I https://source.unsplash.com/random
HTTP/2 503
cache-control: no-cache, no-store
content-type: text/html; charset=utf-8
server: Heroku
via: 2.0 heroku-router
# body: một iframe trỏ đến herokucdn.com/error-pages/application-error.html
Đây không phải lỗi DNS. source.unsplash.com vẫn phân giải được — nó là một CNAME trỏ đến
một host herokudns.com — nên request vẫn được trả lời, chỉ không phải bởi một ứng dụng. Chi tiết
này quan trọng hơn nghe có vẻ: một trình duyệt nhận được 503 nhanh với body HTML
sẽ vẽ ra ảnh giữ chỗ hỏng, và bất kỳ đoạn mã nào đọc response.ok hay một
handler onerror mà bạn chưa từng viết cũng sẽ đi vào nhánh lỗi bạn chưa từng test.
| Mẫu URL | Trước đây trả về gì | Hiện tại |
|---|---|---|
source.unsplash.com/random | Một ảnh ngẫu nhiên, bất kỳ kích thước | 503 |
source.unsplash.com/random/1600x900 | Một ảnh ngẫu nhiên, cắt theo kích thước | 503 |
source.unsplash.com/1600x900/?apple,desk | Một ảnh ngẫu nhiên khớp từ khóa tìm kiếm | 503 |
source.unsplash.com/featured/1600x900?nature | Một ảnh nổi bật ngẫu nhiên | 503 |
source.unsplash.com/collection/190727/800x600 | Một ảnh ngẫu nhiên từ một bộ sưu tập | 503 |
source.unsplash.com/user/scottwebb/1600x900 | Một ảnh ngẫu nhiên từ một nhiếp ảnh gia | 503 |
source.unsplash.com/daily | Ảnh trong ngày | 503 |
Được kiểm tra riêng lẻ bằng curl -o /dev/null -w "%{http_code}". Tính năng tìm kiếm đã bị
gỡ bỏ trước, đúng như thông báo; hiện tại toàn bộ ứng dụng đã tắt, nên sự phân biệt đó không còn
tồn tại.
Ba năm giữa “deprecated” và “off”
Cả hai thông báo vẫn đọc được, ở cùng một nơi, tại unsplash.com/documentation/changelog. Trích dẫn đầy đủ, vì câu chữ chính là toàn bộ câu chuyện:
25 tháng 11 năm 2021 — “Unsplash Source being deprecated”
“Unsplash Source đang được deprecate. Các cách dùng hiện tại sẽ tiếp tục hoạt động, tuy nhiên với dự án mới hãy dùng đầy đủ Unsplash API.”
11 tháng 6 năm 2024 — “Unsplash Source sunset”
“Unsplash Source đã chính thức không còn được hỗ trợ kể từ khi deprecate vào năm 2021. Trong khuôn khổ quá trình sunset cuối cùng, trước tiên chúng tôi sẽ thu hẹp dần bằng cách tắt tính năng tìm kiếm, và trong vài tuần tới sẽ tắt hoàn toàn ứng dụng. Các cách dùng Source hiện tại — đặc biệt là ở cấp độ production — nên chuyển sang đầy đủ Unsplash API càng sớm càng tốt.”
Đọc theo trình tự thì kiểu thất bại này rất rõ ràng. Thông báo năm 2021 chứa một lời hứa (các cách dùng hiện tại sẽ tiếp tục hoạt động) và không có ngày cụ thể. Một lập trình viên đọc nó vào năm 2021 có mọi lý do để để yên đoạn mã đang chạy; một lập trình viên gia nhập năm 2022 chưa từng đọc nó. Thông báo năm 2024 đưa ra mốc “vài tuần tới”, ba năm sau đó, trên một trang không ai đánh dấu bookmark.
Unsplash có công bố một chính sách deprecation, và đó là một chính sách hợp lý — tài liệu nói rằng
đối với các trường và endpoint được ghi tài liệu công khai, thay đổi sẽ được thông báo trên changelog với ít nhất
3 tuần báo trước, và các endpoint trả về header Warning
trong giai đoạn deprecation. Cùng đoạn văn đó chứa câu giải thích tại sao không điều nào trong số đó
bảo vệ Source: “Đối với bất kỳ trường hay endpoint nào không được ghi tài liệu công khai, chúng tôi có thể thay đổi
mà không cần cảnh báo.” Source chưa bao giờ là một endpoint thuộc API được ghi tài liệu. Nó nằm ngoài
chính sách lẽ ra sẽ bao trùm nó.
- Bài học thực tiễn không phải là “Unsplash bất cẩn”. Mà là một URL bạn có thể dùng mà không cần đọc bất kỳ tài liệu nào cũng là một URL mà chính sách deprecation của nó bạn cũng chưa đọc.
- Hỏng hóc xuất hiện trước cả thông báo. Một issue Drupal được mở vào 28 tháng 11 năm 2022 đã báo cáo “Tôi luôn gặp lỗi Heroku application error”, mười tám tháng trước mục thông báo sunset. Lỗi ngắt quãng chính là cách các dịch vụ như thế này chết: chết dần, rồi chết hẳn trong một thông báo mà bạn không bao giờ thấy.
Thông báo còn khó tìm hơn cả sự cố
Thông báo deprecation năm 2021 được đăng trên changelog.unsplash.com, và đó là URL
mọi báo cáo lỗi cùng thời điểm liên kết đến — bao gồm cả issue Drupal ở trên. Ba
phép đo:
- Endpoint HTTPS bị hỏng.
openssl s_client -connect changelog.unsplash.com:443trả vềtlsv1 alert internal error— bắt tay thất bại trước khi bất kỳ chứng chỉ nào được trình ra. Do đó mọi liên kết từ thời 2021, vốn dùnghttps://, đều chết trên trình duyệt. - Qua HTTP thuần thì có redirect, nhưng không hữu ích. Theo dấu
http://changelog.unsplash.com/deprecations/2021/11/25/source-deprecation.html, sau hai bước nhảy, kết thúc ở lỗi400tạiunsplash.com/@documentation/changelog/deprecations/2021/11/25/source-deprecation/html— đường dẫn đã bị nuốt bởi route username của trang. - Kho lưu trữ có một khoảng trống đúng chỗ mốc sunset. Bản lưu thành công cuối cùng của Wayback Machine cho changelog cũ là ngày 24 tháng 3 năm 2024; bản lưu đầu tiên cho bản mới là ngày 23 tháng 8 năm 2024. Mốc sunset được thông báo vào 11 tháng 6 năm 2024 — nằm trong khoảng trống 5 tháng đó.
Không có gì trong số đó là âm mưu; đó chỉ là một cuộc di dời CMS bình thường. Nhưng hậu quả là có thật, và đó là lý do bài viết này trích dẫn cả hai mục nguyên văn: bản ghi gốc của một deprecation lẽ ra phải sống lâu hơn cái nó deprecate, và ở đây nó suýt nữa đã không làm được điều đó.
Những gì thực sự đã hỏng
Không phải các dự án tay ngang. Các sự cố dưới đây là các mục issue-tracker công khai; tiêu đề, ngày tháng và trạng thái lấy từ API của GitHub và drupal.org.
| Dự án | Issue | Mở lúc | Nội dung |
|---|---|---|---|
| MUI (Material UI) | #42736 | 24 tháng 6 năm 2024 | “[docs] Random Unsplash photo URL is no longer functional” — template chính thức Sign-in side đã phát hành một ảnh chết. Đóng ba ngày sau. |
| Nextcloud | #115 | 17 tháng 1 năm 2023 | “Migrate to Unsplash API” — ứng dụng nền được xây trên các URI của Source. Mở suốt mười tám tháng, đóng ngày 16 tháng 7 năm 2024. |
| sindresorhus/Actions | #248 | 28 tháng 5 năm 2024 | “Get Unsplash Image: 503 Error” — một Shortcuts action trên iOS/macOS, hỏng hai tuần trước khi mốc sunset được thông báo. |
| Drupal — Gin Login | #3324054 | 28 tháng 11 năm 2022 | “Unsplash đã deprecate source.unsplash.com — điều này làm trì hoãn việc tải reCAPTCHA, khiến người dùng không thể đăng nhập.” |
Đọc lại dòng cuối, vì đó là dòng đáng để khắc cốt ghi tâm. Một ảnh trang trí cạnh form đăng nhập — tài nguyên rõ ràng không quan trọng nhất trên trang — lại thoái hóa thành một sự cố xác thực, bởi vì một request bên thứ ba chậm chạp nằm chắn trước CAPTCHA mà form đăng nhập cần. Không ai viết ra nó theo cách đó. Nó nảy sinh từ thứ tự mà trình duyệt tải các thành phần.
Trợ lý code của bạn không nhận được thông báo
Đây là phần biến một sự cố năm 2024 thành vấn đề năm 2026. source.unsplash.com
đã được ghi tài liệu, viết blog, giảng dạy và sao chép suốt khoảng tám năm. Toàn bộ văn bản đó nằm trong
dữ liệu huấn luyện của các mô hình hiện đang viết mã khởi đầu cho chúng ta — và văn bản không hết hạn. Ba
con số:
| Phép đo | Giá trị vào ngày 30 tháng 8 năm 2026 | Cách đo |
|---|---|---|
Số tệp chứa source.unsplash.com |
3.344 | API tìm kiếm mã của GitHub, q=source.unsplash.com (chỉ mã công khai đã được index — là con số sàn, không phải tổng) |
| Số repository trong mẫu 100 tệp được tạo sau mốc sunset | 12/77 | Cùng truy vấn, 100 kết quả, khử trùng lặp còn 77 repository, so sánh created_at với ngày 11 tháng 6 năm 2024 |
| …và số repository trong mẫu đó có push trong 12 tháng gần nhất | 20/77 | Repository đang hoạt động, không phải archive — bao gồm elastic/kibana, mà tệp demo vẫn có dòng imageUrl: 'https://source.unsplash.com/64x64/?dingo' |
Lượt tải hàng tháng của unsplash-source-es6 |
23 | API registry của npm — một wrapper cho một dịch vụ đã chết, xuất bản lần cuối năm 2022, vẫn đang được cài đặt |
| Bài viết Stack Overflow đề cập đến nó | 1.459 | API Stack Exchange, tổng số từ /search/excerpts |
Bằng chứng trực tiếp nhất không hề nằm trong mã ứng dụng — mà nằm trong prompt. Kết quả hàng đầu cho truy vấn đó là một thư viện các system prompt của GPT chứa dòng “please use unsplash API( https://source.unsplash.com/1280x720/?<PUT YOUR QUERY HERE>”. Chỉ dẫn đó vẫn đang được sao chép vào các trợ lý mới ngày nay. Mô hình không xác minh URL; nó được bảo dùng URL đó, và mọi ví dụ nó từng thấy đều đồng thuận.
Vì vậy sự cố mã sinh ra có hai nguyên nhân độc lập, và sửa một cái không sửa được cái kia: dữ liệu huấn luyện lỗi thời, và các chỉ dẫn lỗi thời do con người viết chồng lên nó. Dù thế nào, triệu chứng vẫn là cùng một họ ảnh không bao giờ tải được:
source.unsplash.com/random/1200x800 # 503 từ giữa 2024 — không bao giờ trở lại
images.unsplash.com/photo-… # CDN thật, nhưng ID nhớ nằm lòng có thể không tồn tại
via.placeholder.com/400 # hình chữ nhật xám, đã lên production
placehold.co/800x600 # hình chữ nhật xám, có chủ đích
picsum.photos/800/600 # một ảnh thật, không liên quan đến trang của bạn
/placeholder.png # một tệp chưa bao giờ được thêm vào repo
Một chú thích cho dòng thứ hai trong danh sách đó: khi viết bài này, via.placeholder.com
cũng không hoàn tất được bắt tay TLS từ mạng thử nghiệm của chúng tôi, và trả về 403
qua HTTP thuần. Hãy kiểm tra từ mạng của chính bạn trước khi tin tưởng nó — phương án dự phòng mà
các công cụ này tìm tới có thể có câu chuyện sự cố riêng.
Chỉ có cái đầu tiên là hỏng. Các cái còn lại tệ theo cách tinh vi hơn: chúng tải được, layout trông như đã hoàn thiện, và không ai nhận ra rằng trang được minh họa bằng thứ chẳng liên quan gì cụ thể. Và điều này không chỉ đúng với ảnh — đó là hình dạng chung của vấn đề. Bức tranh của một mô hình về web là một tấm ảnh chụp nhanh, còn endpoint, cờ CLI, tên package và gói miễn phí thì vẫn tiếp tục thay đổi sau khi màn trập đã đóng lại.
Bảng migrate
Có đúng ba điểm đến, và cách trình bày trung thực nhất là theo những gì bạn phải đánh đổi. Chọn cột trước, rồi đọc dòng của bạn.
| URL Source cũ | A. URL CDN cố địnhkhông key · không ngẫu nhiên | B. Unsplash APIcó key · gọi phía server | C. Proxy tự vận hànhkey được giấu · lấy lại tính ngẫu nhiên |
|---|---|---|---|
/random |
images.unsplash.com/photo-… — một ảnh bạn tự chọn |
GET /photos/random |
/?w=1600 |
/random/1600x900 |
…?w=1600&h=900&fit=crop |
/photos/random + tham số Imgix trên URL trả về |
/?w=1600&h=900&fit=crop |
/1600x900/?apple,desk |
Không có tương đương — tự chọn ảnh bằng tay | /photos/random?query=apple,desk |
/?query=apple,desk&w=1600 |
/featured/1600x900?nature |
Không có tương đương | /photos/random?query=nature “featured” không có phiên bản kế thừa |
/?query=nature&w=1600 |
/collection/67920491/1600x900 |
Không có tương đương | /photos/random?collections=67920491 |
/?collections=67920491&w=1600 |
/user/scottwebb/1600x900 |
Không có tương đương | /photos/random?username=scottwebb |
/?username=scottwebb&w=1600 |
/daily |
Ghim một ảnh, xoay vòng khi build | Không có tương đương — tự cache một ảnh ngẫu nhiên trong 24 giờ | Tương tự, với cache nằm trong proxy |
Phương án A là phương án mà đa số mọi người thực sự muốn. Nếu ảnh chỉ mang tính trang trí — một hero, một panel bên cạnh form đăng nhập, một background thẻ — bạn chưa bao giờ thực sự cần một ảnh khác cho mỗi request. Chọn một ảnh, giữ URL CDN, và trang sẽ ngừng phụ thuộc vào bất kỳ thứ gì ngẫu nhiên:
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1600&h=900&fit=crop&auto=format"
width="1600" height="900" alt="…">
# Các tham số được hỗ trợ chính thức: w, h, crop, fit, fm, auto=format, q, dpr.
# Giữ nguyên tham số ixid mà API cấp cho bạn — đó là thứ báo cáo lượt xem.
Phương án B là con đường chính thức, và nó chuyển lệnh gọi sang phía server, bởi vì
một Client-ID nằm trong JavaScript phía front-end là một thông tin xác thực đã bị công khai. Chú ý hai quy tắc
dễ khiến người ta vấp: collections/topics không thể kết hợp với
query trong cùng một request, và count (tối đa 30) làm thay đổi cấu trúc phản hồi
thành một mảng ngay cả khi giá trị là 1.
curl "https://api.unsplash.com/photos/random?query=nature&orientation=landscape" \
-H "Authorization: Client-ID YOUR_ACCESS_KEY" \
-H "Accept-Version: v1"
# → JSON. Ảnh nằm ở .urls.regular / .urls.raw (tự thêm w/h/fit).
# → X-Ratelimit-Limit: 1000 X-Ratelimit-Remaining: 999
Phương án C: xây lại Source, trong khoảng bốn mươi dòng
Nếu điều bạn thực sự mất là hành vi — một URL không cần key, trả về một ảnh khác nhau mỗi
lần, dùng được ngay từ thẻ <img>, trong một trường CMS, hay trong một trang tĩnh
không có server — thì bạn phải tự vận hành endpoint đó. Đó là một worker nhỏ đứng
trước API, và ba thứ giúp nó sống sót khi va chạm với production là cache,
kiểm tra referrer, và truyền phần còn lại của query string sang CDN.
// Cloudflare Workers. Ở nơi khác (Deno Deploy, Val Town…) cấu trúc tương tự,
// nhưng hãy mở một cache có tên bằng caches.open() thay vì caches.default.
// UNSPLASH_KEY luôn ở phía server. Người gọi không bao giờ thấy nó.
const ALLOWED = ["example.com", "www.example.com"]; // chỉ domain của bạn
const API_PARAMS = ["query", "collections", "topics", "username", "orientation"];
const TTL = 60; // giây — bảo vệ quota hàng giờ
const host = (value) => { try { return new URL(value).hostname; } catch { return null; } };
export default {
async fetch(req, env, ctx) {
// 0. Chỉ GET: Cache API từ chối lưu bất kỳ phương thức nào khác, và một endpoint
// ảnh cũng không có động từ nào khác để trả lời.
if (req.method !== "GET")
return new Response("Method not allowed", { status: 405 });
const url = new URL(req.url);
// 1. Chỉ trang của riêng bạn mới được nhúng nó — một endpoint ảnh ngẫu nhiên
// công khai trên internet là quota rate limit của người khác bị đốt.
const ref = req.headers.get("referer"); // vắng mặt ở khá nhiều client hợp lệ
if (ref && !ALLOWED.includes(host(ref)))
return new Response("Forbidden", { status: 403 });
// 2. Cache theo từng tổ hợp tham số, để một trang với 12 ảnh
// tốn một lệnh gọi API mỗi phút thay vì mười hai lần mỗi lần render.
const cache = caches.default;
const hit = await cache.match(req);
if (hit) return hit;
// 3. Hỏi API chính thức một ảnh ngẫu nhiên.
const api = new URL("https://api.unsplash.com/photos/random");
for (const p of API_PARAMS)
if (url.searchParams.has(p)) api.searchParams.set(p, url.searchParams.get(p));
const r = await fetch(api, { headers: {
Authorization: "Client-ID " + env.UNSPLASH_KEY,
"Accept-Version": "v1",
}});
// 403 ở đây thường có nghĩa là quota hàng giờ, không phải key sai — tăng TTL, đừng hoảng.
if (!r.ok) return new Response("Upstream " + r.status, { status: 502 });
const photo = await r.json();
// 4. Dựng lại URL ảnh: giữ ixid, thêm các tham số kích thước của người gọi.
const img = new URL(photo.urls.raw); // .raw đã có sẵn ixid
for (const [k, v] of url.searchParams)
if (!API_PARAMS.includes(k)) img.searchParams.set(k, v); // w, h, fit, q…
const res = new Response(null, { status: 302, headers: {
Location: img.toString(),
"Cache-Control": "public, max-age=" + TTL,
// Credit đi kèm redirect; giá trị header phải là ASCII, nên phải encode.
"X-Photo-Credit": encodeURIComponent(photo.user.name + " on Unsplash"),
"X-Photo-Link": photo.links.html,
}});
ctx.waitUntil(cache.put(req, res.clone()));
return res;
},
};
Migrate một URL khi đó chỉ còn là tìm-và-thay, và đó chính xác là lý do phương án này đáng bỏ ra hai mươi phút:
- https://source.unsplash.com/collection/67920491/1600x900
+ https://img.example.com/?collections=67920491&w=1600&h=900&fit=crop
Hai lưu ý về thiết kế, cả hai đều từng khiến ai đó có một buổi chiều tồi tệ trước khi được ghi lại.
Redirect (302) thay vì proxy dữ liệu byte giúp bạn không phải gánh băng thông
và vẫn giữ lượt xem được tính trên CDN của Unsplash, đúng như hướng dẫn yêu cầu. Còn
kiểm tra Referer cố tình rộng rãi khi header vắng mặt — khá nhiều client
hợp lệ tự loại bỏ nó — trong khi vẫn ngăn được trường hợp rõ ràng nhất là endpoint của bạn trở thành
một API ảnh miễn phí cho người khác.
Các quy tắc bạn thừa hưởng ngay khi dùng API
Source không có quy tắc nào vì nó không có tài khoản. API có năm quy tắc làm thay đổi cách bạn thiết kế kiến trúc, tất cả đều lấy từ tài liệu hiện hành:
- Giới hạn tần suất tính theo giờ, và ban đầu khá nhỏ. 50 request/giờ
ở chế độ demo; 1.000/giờ sau khi ứng dụng của bạn được duyệt cho production. Chỉ các
lệnh gọi tới
api.unsplash.commới được tính — request ảnh tớiimages.unsplash.comthì không. ĐọcX-Ratelimit-Remainingở mọi phản hồi. - Hotlink là bắt buộc, không chỉ là được phép. Unsplash yêu cầu các URL ảnh mà API trả về phải được nhúng trực tiếp, để lượt xem ảnh có thể được ghi nhận cho nhiếp ảnh gia. Mirror tệp lên CDN riêng của bạn là tối ưu duy nhất bạn không được phép làm.
- Giữ nguyên tham số
ixid. Đổi kích thước và cắt URL trả về là chuyện được chấp nhận; loại bỏ tham số nhận diện ứng dụng của bạn thì không. - Ghi nguồn và theo dõi lượt tải là một phần của thỏa thuận — nhiếp ảnh gia và Unsplash được ghi nhận công lao, và một “lượt tải” được báo cáo qua endpoint download của ảnh khi người dùng lấy tệp, đây là một sự kiện bạn phải tự bắn ra.
- Sản phẩm phân phối rộng cần đăng ký client động. Nếu bạn phát hành một plugin, một theme hay một CMS tự host, một key dùng chung vừa vi phạm chính sách vừa là điểm lỗi đơn nhất; API có một luồng đăng ký dành riêng cho trường hợp đó.
Đây là lúc để thẳng thắn về phạm vi: nếu dù sao bạn cũng đang nối dây một API và một key, thì lựa chọn API ảnh nào bỗng dưng lại mở ra, và đáng để bỏ ra năm phút suy nghĩ trước khi viết client. Chúng tôi đã so sánh các dịch vụ miễn phí — quota, quy tắc, hành vi tìm kiếm và cấu trúc phản hồi — trong bài so sánh API ảnh stock miễn phí.
Nếu bạn chỉ cần một ảnh giữ chỗ, hãy nói vậy
Phần lớn việc dùng Source chưa bao giờ thực sự liên quan đến Unsplash. Đó chỉ là “đặt tạm cái gì đó có hình dạng ảnh vào đây trong lúc tôi dựng layout”. Cho mục đích đó, các dịch vụ không cần key vẫn tồn tại và là câu trả lời đúng:
| Dịch vụ | Cần key? | Bạn nhận được gì | Giới hạn ở đâu |
|---|---|---|---|
| Lorem Picsum | Không | Ảnh chụp thật: picsum.photos/800/600, cố định với /id/237/… hoặc /seed/xxx/…, cộng thêm ?grayscale và ?blur=1..10. Endpoint /v2/list ghi nhận trang Unsplash và tác giả của từng ảnh. |
Không nhắm mục tiêu chủ đề. Ảnh sẽ không liên quan đến trang của bạn. |
| placehold.co | Không | Hình chữ nhật có nhãn ở bất kỳ kích thước nào — bộ lấp đầy wireframe trung thực. | Nó là một hộp xám, và trông y như vậy trong screenshot chia sẻ với khách hàng. |
| Openverse | Không | Một danh mục cấp phép mở với API công khai, do WordPress.org vận hành. | Khớp theo từ khóa, và giấy phép khác nhau theo từng mục — bạn phải tự đọc chúng. |
Sự khác biệt quan trọng: một ảnh giữ chỗ, theo định nghĩa, là tạm thời. Nếu ảnh đó sống sót tới production, nó không còn là ảnh giữ chỗ nữa — nó là một minh họa mà không ai chọn, và người đọc có thể nhận ra điều đó.
Một key thay vì ba
Đây là phần của việc migrate mà không ai lên kế hoạch trước: những người rời Source hiếm khi
dừng lại ở một API. Một trang cần một hero, hai ảnh section và thứ gì đó cho một lưới thẻ, và
câu trả lời trung thực thường là Unsplash cộng Pexels cộng Pixabay — ba
lần đăng ký, ba cơ chế xác thực, ba cấu trúc JSON, ba mô hình phân trang và ba bộ
quy tắc ghi nguồn, tất cả chỉ để lấp đầy cùng những thẻ <img>. Khối lượng tích hợp đó
chính là hóa đơn thực sự cho việc một URL không cần key biến mất, và nó đến vài tuần sau sự cố
đã gây ra nó.
Gộp toàn bộ điều đó thành một tích hợp duy nhất là lý do chúng tôi xây dựng Pexafy: một key duy nhất trên 9 thư viện cấp phép miễn phí trong một schema, với tìm kiếm ngữ nghĩa cấp câu — để một mô tả đầy đủ như “một màn hình điện thoại nứt trên bàn gỗ, chụp từ trên xuống” trả về kết quả xếp hạng thay vì không có gì cả. Hai giới hạn, nói thẳng, vì cả bài viết này là về việc không bị bất ngờ lần thứ hai: nó cần một key, nên nó không khôi phục lại những gì Source từng có — nó thuộc cùng nhóm với Unsplash API chính thức ở trên; và nó mang theo ảnh cấp phép miễn phí, không phải ảnh biên tập hay ảnh thương hiệu.
Phần thực sự mới nhắm tới các trợ lý ở trên: một MCP
server tại mcp.pexafy.com/mcp có nghĩa là một mô hình lẽ ra sẽ đọc thuộc lòng một URL ảnh
từ trí nhớ giờ có thể tìm kiếm một danh mục thật và trả về một ảnh tồn tại thật, kèm dòng ghi nguồn
đi cùng. Đó là câu trả lời tốt hơn cho vấn đề URL chết do máy viết ra so với bất kỳ lint rule nào, và
lập luận được trình bày trong
hạ tầng tìm kiếm ảnh cho AI agent.
Kiểm kê hệ thống của bạn trong mười phút
Dù bạn migrate sang đâu, hãy làm phần này trước tiên — bạn không thể sửa các URL mình chưa tìm ra. Source chỉ là ví dụ của hôm nay; cùng ba bước đó áp dụng cho mọi tài nguyên bên ngoài bạn nhúng vào.
# 1. Mọi thứ trong repo, gồm cả docs, test, fixture và README.
grep -rn --binary-files=without-match \
-e "source.unsplash.com" -e "via.placeholder.com" -e "/placeholder.png" .
# 2. Mọi thứ database đang lưu — nội dung CMS là nơi những thứ này ẩn náu lâu nhất.
psql -c "SELECT id FROM posts WHERE body LIKE '%source.unsplash.com%'"
# 3. Mọi thứ trang đã build thực sự yêu cầu: crawl nó và liệt kê các lỗi.
# Khớp theo attribute, không theo đuôi tệp — URL ảnh hiếm khi kết thúc bằng .jpg.
grep -rhoE 'src="[^"]+"' dist/ \
| cut -d'"' -f2 | grep -E '^https?://' | sort -u \
| xargs -P8 -I{} curl -s -o /dev/null -w "%{http_code} {}\n" {} \
| grep -v "^200"
# 503 https://source.unsplash.com/random/1200x800 ← đây là thứ bạn đang tìm
Sau đó hãy quyết định, một lần cho tất cả, ảnh bên ngoài được phép làm bạn tốn kém bao nhiêu. Bốn quy tắc sống sót qua lần sập tiếp theo, bất kể ai gây ra nó:
- Lấy ảnh lúc build, không phải lúc request. Một ảnh được phân giải trong lúc build sẽ hỏng trong CI, trước mắt lập trình viên, thay vì lúc 3 giờ sáng trước mắt người dùng.
- Đừng bao giờ để một tài nguyên trang trí chặn một luồng quan trọng. Đừng preload
bất kỳ thứ gì bên ngoài phía trên form đăng nhập; hãy cho mọi
<img>bên thứ ba một fallbackonerrorvàwidth/heighttường minh để một lỗi chỉ tốn một hộp trống, không phải một cú giật layout hay một script bị treo. - Thêm kiểm tra vào CI. Bước 3 ở trên, chạy trên output đã build của bạn, biến “ai đó cuối cùng cũng nhận ra” thành một build đỏ. Đó là bước duy nhất ngăn tái diễn.
- Lập ngân sách cho các phụ thuộc bên ngoài như bất kỳ phụ thuộc nào khác. Ghi lại các host mà trang của bạn được phép phụ thuộc vào và chuyện gì xảy ra khi mỗi host đó gặp sự cố. Một URL bạn không cần đăng ký vẫn là một phụ thuộc — Source đã chứng minh nó đơn giản là một phụ thuộc không ai sở hữu.
Tham khảo & chú thích
1 Mọi mã trạng thái, số liệu và dòng trích dẫn trong bài viết này
đều được lấy từ nguồn gốc vào ngày 30 tháng 8 năm 2026. Mã trạng thái HTTP
được lấy bằng curl đối với từng mẫu URL; tất cả đều trả về 503 kèm
server: Heroku và một body nhúng
herokucdn.com/error-pages/application-error.html. Phân giải DNS được xác nhận cùng
ngày (một CNAME trỏ tới một host herokudns.com).
2 Cả hai mục changelog được trích dẫn nguyên văn từ
unsplash.com/documentation/changelog. Nội dung chính sách deprecation (3 tuần báo trước, header Warning, và ngoại lệ cho
các endpoint không được ghi tài liệu công khai) đến từ unsplash.com/documentation, cùng ngày.
3 Lỗi TLS được tái hiện bằng
openssl s_client -connect changelog.unsplash.com:443 (tlsv1 alert internal
error). Chuỗi redirect được theo dấu bằng curl -L. Khoảng trống archive lấy từ
API CDX của Wayback: bản lưu 200 cuối cùng của changelog.unsplash.com vào
20240324, bản đầu tiên của unsplash.com/documentation/changelog vào 20240823.
4 Tiêu đề issue, ngày tạo và ngày đóng được đọc từ API REST của GitHub
(mui/material-ui#42736, nextcloud/unsplash#115,
sindresorhus/Actions#248) và từ API JSON của drupal.org cho
issue 3324054 của gin_login, mà nội dung báo cáo “Tôi luôn gặp lỗi Heroku application
error” vào tháng 11 năm 2022.
5 Số liệu: API tìm kiếm mã của GitHub
(3.344 tệp; mẫu 100 kết quả khử trùng lặp còn 77 repository, trong đó 12 được
tạo sau ngày 11 tháng 6 năm 2024 và 20 đã có push trong 12 tháng trước đó);
API lượt tải registry của npm (unsplash-source-es6, 23 lượt tải trong 30 ngày
trước đó); Stack Exchange /search/excerpts (1.459 bài viết). Tìm kiếm mã
chỉ bao phủ các repository công khai đã được index, nên mỗi con số là một mức sàn.
Nguồn gốc: Unsplash API changelog · Unsplash API documentation · Unsplash attribution guideline · Unsplash status · MUI #42736 · Nextcloud #115 · sindresorhus/Actions #248 · Drupal Gin Login #3324054 · Lorem Picsum · Openverse · Pexafy API & MCP docs.
Câu hỏi thường gặp
source.unsplash.com bị lỗi tạm thời hay đã bị tắt vĩnh viễn?
/random, /1600x900/?query, /collection/…, /daily) đều trả về HTTP 503 với trang lỗi chung Application Error của Heroku. Tên miền vẫn phân giải được, nên lỗi này hiển thị dưới dạng ảnh hỏng thay vì lỗi mạng.Đâu là giải pháp thay thế trực tiếp cho source.unsplash.com/random?
images.unsplash.com/photo-…?w=1600&h=900&fit=crop — không cần key nhưng luôn trả về cùng một bức ảnh, điều mà hầu hết các mục đích trang trí thực ra chỉ cần vậy. API chính thức, GET https://api.unsplash.com/photos/random với header Authorization: Client-ID, khôi phục tính ngẫu nhiên nhưng phải được gọi từ phía server. Một proxy nhỏ do bạn tự vận hành đặt trước endpoint đó là lựa chọn duy nhất trả lại cho bạn một URL không cần key mà bạn có thể chèn thẳng vào thẻ <img>.Tại sao các công cụ lập trình AI vẫn tạo ra URL source.unsplash.com vào năm 2026?
Tôi có thể lấy ảnh Unsplash ngẫu nhiên mà không cần API key nữa không?
/photos/random, yêu cầu Client-ID. Hai hướng đi không cần key của bạn là một proxy bạn tự lưu trữ, nơi key được giữ ở phía server còn URL công khai trông giống URL cũ, hoặc một dịch vụ ảnh giữ chỗ của bên thứ ba như Lorem Picsum (picsum.photos/800/600), phục vụ ảnh chụp thật không cần key nhưng hoàn toàn không có khả năng nhắm chủ đề.API của Unsplash có cho phép tải xuống và tự lưu trữ ảnh không?
ixid khi bạn thay đổi kích thước hoặc cắt URL, ghi công nhiếp ảnh gia và Unsplash, và gọi endpoint tải xuống của ảnh khi người dùng tải tệp về. Sao chép các tệp lên CDN riêng của bạn là điều tối ưu duy nhất bạn không được phép làm.Tại sao thông báo ngừng hỗ trợ năm 2021 lại không bảo vệ được người dùng hiện có?
Warning — chỉ áp dụng cho các trường và endpoint được ghi trong tài liệu công khai, và cũng chính đoạn đó nói rõ rằng bất cứ điều gì không được ghi trong tài liệu có thể thay đổi mà không cần cảnh báo. Source chưa bao giờ là một endpoint được ghi trong tài liệu chính thức của API, nên nó nằm ngoài phạm vi bảo vệ của chính sách đó.Làm sao để tìm tất cả các URL source.unsplash.com đã chết trong dự án của tôi?
grep -rn "source.unsplash.com" . — vì các URL này tồn tại lâu nhất trong mã mẫu. Truy vấn cơ sở dữ liệu, bởi nội dung bài viết CMS là nơi chúng thường ẩn náu (WHERE body LIKE '%source.unsplash.com%'). Sau đó quét đầu ra đã build: trích xuất mọi URL ảnh và gửi yêu cầu đến từng cái, liệt kê những cái không trả về 200. Thêm bước cuối này vào CI để một tài nguyên bên thứ ba đã chết làm build thất bại thay vì làm hỏng trang.