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.

Chia sẻ
Ảnh đen trắng của một chiếc điện thoại thông minh với màn hình nứt nằm trên bề mặt gỗ màu nhạt, logo Unsplash hiện trên màn hình bị hỏng.
Ảnh từ Unsplash

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:

terminal
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/randomMột ảnh ngẫu nhiên, bất kỳ kích thước503
source.unsplash.com/random/1600x900Một ảnh ngẫu nhiên, cắt theo kích thước503
source.unsplash.com/1600x900/?apple,deskMột ảnh ngẫu nhiên khớp từ khóa tìm kiếm503
source.unsplash.com/featured/1600x900?natureMột ảnh nổi bật ngẫu nhiên503
source.unsplash.com/collection/190727/800x600Một ảnh ngẫu nhiên từ một bộ sưu tập503
source.unsplash.com/user/scottwebb/1600x900Một ảnh ngẫu nhiên từ một nhiếp ảnh gia503
source.unsplash.com/dailyẢnh trong ngày503

Đượ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:

  1. Endpoint HTTPS bị hỏng. openssl s_client -connect changelog.unsplash.com:443 trả 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ùng https://, đều chết trên trình duyệt.
  2. 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ỗi 400 tại unsplash.com/@documentation/changelog/deprecations/2021/11/25/source-deprecation/html — đường dẫn đã bị nuốt bởi route username của trang.
  3. 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 đoGiá trị vào ngày 30 tháng 8 năm 2026Cá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:

các mẫu ảnh chết đáng để grep
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:

một URL Unsplash cố định, có thể đổi kích thước — không key, không gọi API
<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.

giải pháp thay thế chính thức cho /random
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.

worker.js — một endpoint không cần key, hình dạng như Source, đặt trên /photos/random
// 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:

thay thế một-đổi-mộ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.com mới được tính — request ảnh tới images.unsplash.com thì không. Đọc X-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.

tìm mọi tham chiếu chết, rồi giữ chúng không quay lại
# 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ó:

  1. 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.
  2. Đừ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 fallback onerror và width/height tườ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.
  3. 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.
  4. 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.

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?
Vĩnh viễn. Unsplash đã thông báo ngừng hoạt động vào ngày 11 tháng 6 năm 2024 — “chúng tôi sẽ bắt đầu bằng việc vô hiệu hóa tính năng tìm kiếm, và trong những tuần tới sẽ tắt hẳn ứng dụng” — sau khi ngừng hỗ trợ dịch vụ này vào ngày 25 tháng 11 năm 2021. Mọi mẫu URL (/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?
Không có giải pháp thay thế nào không cần API key, và đây là điều bạn nên chấp nhận sớm. Có ba giải pháp thay thế. Một URL CDN cố định — 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?
Bởi vì URL này đã được ghi tài liệu, giảng dạy và sao chép trong khoảng tám năm, và một tập dữ liệu huấn luyện không hết hạn khi dịch vụ ngừng hoạt động. Đo lường vào ngày 30 tháng 8 năm 2026: tìm kiếm mã nguồn trên GitHub vẫn trả về 3.344 tệp chứa URL này, và trong một mẫu 100 tệp, 12 trên 77 kho lưu trữ được tạo sau khi dịch vụ bị tắt. Các thư viện prompt do con người viết cũng lặp lại chỉ dẫn này — một prompt GPT được sao chép rộng rãi vẫn bảo mô hình “dùng unsplash API( https://source.unsplash.com/1280x720/?… )”. Hãy coi mọi URL ảnh do mô hình AI viết ra là chưa được xác minh và kiểm tra mã trạng thái trong CI.
Tôi có thể lấy ảnh Unsplash ngẫu nhiên mà không cần API key nữa không?
Không thể trực tiếp từ Unsplash — việc chọn ảnh ngẫu nhiên giờ nằm sau /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?
Không. Khác với hầu hết các API, Unsplash yêu cầu hotlinking: các URL ảnh mà API trả về phải được nhúng trực tiếp, để lượt xem ảnh được tính cho nhiếp ảnh gia. Điều này đi kèm ba nghĩa vụ — giữ nguyên tham số 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ó?
Bởi vì thông báo và chính sách không nói về cùng một điều. Mục changelog ngày 25 tháng 11 năm 2021 hứa rằng “các trường hợp sử dụng hiện có sẽ tiếp tục hoạt động” và không đưa ra ngày kết thúc nào. Chính sách ngừng hỗ trợ được công bố của Unsplash — ít nhất ba tuần thông báo trước cộng với header 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?
Ba bước, mười phút. Grep toàn bộ kho lưu trữ bao gồm cả tài liệu, test, fixture và README — 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.

Đừng săn lùng từ khóa nữa. Hãy mô tả điều bạn muốn nói.

Tìm kiếm 9M+ ảnh miễn phí sử dụng được theo ý nghĩa — trong mọi ngôn ngữ, dưới 100 ms.