source.unsplash.com a disparu : le post-mortem et toutes les façons de le remplacer
Dépréciée en 2021 avec la promesse que « les usages existants continueront de fonctionner », coupée en juin 2024, et pourtant encore écrite dans du code neuf aujourd'hui. Le post-mortem mesuré — et les trois solutions de remplacement, dont le proxy qui redonne l'aléatoire sans clé API.
Un service de photos stock a désactivé un sous-domaine, et deux ans plus tard, celui-ci continue de casser des sites de documentation, des écrans de connexion, des exercices de cours et du code fraîchement généré. Voici l'autopsie d'une URL — ce qu'elle faisait, ce qui l'a tuée, et ce qu'il faut mettre à sa place — accompagnée d'un regard mesuré sur la partie la plus étrange de l'histoire : les machines qui écrivent notre code n'ont pas remarqué sa disparition.
Les réponses HTTP, les deux entrées de changelog citées mot pour mot, les limites de l'API, les gestionnaires de tickets des projets qui ont cassé, ainsi que les décomptes issus de GitHub, npm et Stack Overflow proviennent tous de sources primaires, avec la méthode pour chacune dans les notes de bas de page. Si vous voulez uniquement la solution, allez directement à la table de migration.
Ce que vous obtenez aujourd'hui, si vous le demandez encore
Une seule commande, sans clé, reproductible depuis n'importe quelle machine :
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
# corps : un iframe pointant vers herokucdn.com/error-pages/application-error.html
Rien ici n'est un échec DNS. source.unsplash.com se résout toujours — c'est un CNAME vers un hôte herokudns.com — donc la requête reçoit une réponse, simplement pas d'une application. Ce détail compte plus qu'il n'y paraît : un navigateur qui reçoit rapidement un 503 avec un corps HTML affiche un espace réservé d'image cassée, et tout code qui lit response.ok ou un gestionnaire onerror que vous n'avez jamais écrit emprunte le chemin d'échec que vous n'avez jamais testé.
| Motif d'URL | Ce qu'il renvoyait autrefois | Aujourd'hui |
|---|---|---|
source.unsplash.com/random | Une photo aléatoire, toute taille | 503 |
source.unsplash.com/random/1600x900 | Une photo aléatoire, recadrée à la taille voulue | 503 |
source.unsplash.com/1600x900/?apple,desk | Une photo aléatoire correspondant à des termes de recherche | 503 |
source.unsplash.com/featured/1600x900?nature | Une photo aléatoire mise en avant | 503 |
source.unsplash.com/collection/190727/800x600 | Une photo aléatoire issue d'une collection | 503 |
source.unsplash.com/user/scottwebb/1600x900 | Une photo aléatoire d'un photographe donné | 503 |
source.unsplash.com/daily | La photo du jour | 503 |
Vérifié individuellement avec curl -o /dev/null -w "%{http_code}". La fonction de recherche avait été arrêtée en premier, comme annoncé ; aujourd'hui, l'application entière est désactivée, donc la distinction n'a plus lieu d'être.
Trois ans entre « déprécié » et « éteint »
Les deux annonces restent lisibles, au même endroit, sur unsplash.com/documentation/changelog. Citées intégralement, car la formulation est toute l'histoire :
25 novembre 2021 — « Unsplash Source being deprecated »
« Unsplash Source is being deprecated. Existing uses will continue to work, however for new projects use the full Unsplash API. »
11 juin 2024 — « Unsplash Source sunset »
« Unsplash Source has been officially unsupported since its deprecation in 2021. As part of the final sunsetting, we will first wind down by disabling the search feature, and in the coming weeks turn off the application entirely. Existing uses of Source — particularly production-level ones — should migrate as soon as possible to the full Unsplash API. »
Lues dans l'ordre, la source de l'échec devient évidente. L'avis de 2021 contenait une promesse (existing uses will continue to work) et aucune date. Un développeur qui l'a lu en 2021 avait toute raison de laisser son code fonctionnel tel quel ; un développeur arrivé en 2022 ne l'a jamais lu du tout. L'avis de 2024 a donné « les prochaines semaines », trois ans plus tard, sur une page que personne n'avait mise de côté.
Unsplash publie bien une politique de dépréciation, et elle est raisonnable — la documentation indique que pour les champs et points d'accès publiquement documentés, les changements sont annoncés sur le changelog avec au moins
3 semaines de préavis, et les points d'accès renvoient un en-tête Warning pendant la période de dépréciation. Le même paragraphe contient la phrase qui explique pourquoi rien de tout cela ne protégeait Source : « For any non-publicly documented fields or endpoints, we may make changes to these with no warning. » Source n'a jamais été un point d'accès de l'API documentée. Il se situait en dehors de la politique qui l'aurait couvert.
- La leçon pratique n'est pas « Unsplash a été négligent ». C'est qu'une URL que l'on peut utiliser sans lire aucune documentation est une URL dont on n'a pas non plus lu la politique de dépréciation.
- La casse a précédé l'annonce. Un ticket Drupal déposé le 28 novembre 2022 signale déjà « I get always a Heroku application error », dix-huit mois avant l'entrée de retrait. C'est ainsi que ces services meurent : lentement, puis dans une annonce que vous ne verrez jamais.
L'annonce est plus difficile à trouver que la panne
La dépréciation de 2021 a été publiée sur changelog.unsplash.com, et c'est l'URL vers laquelle pointe tout rapport de bug contemporain — y compris celui de Drupal ci-dessus. Trois mesures :
- Le point d'accès HTTPS est cassé.
openssl s_client -connect changelog.unsplash.com:443renvoietlsv1 alert internal error— la poignée de main échoue avant même la présentation d'un certificat. Chaque lien de l'ère 2021, qui était enhttps://, est donc mort dans un navigateur. - En HTTP simple, il redirige, mais sans utilité. Suivre
http://changelog.unsplash.com/deprecations/2021/11/25/source-deprecation.htmlaboutit, deux sauts plus tard, à un400surunsplash.com/@documentation/changelog/deprecations/2021/11/25/source-deprecation/html— le chemin a été absorbé par la route de nom d'utilisateur du site. - L'archive présente un trou exactement là où se trouve le retrait. La dernière capture réussie de l'ancien changelog par la Wayback Machine date du 24 mars 2024 ; sa première capture du nouveau date du 23 août 2024. Le retrait a été annoncé le 11 juin 2024 — à l'intérieur de cet écart de cinq mois.
Rien de tout cela n'est une conspiration ; c'est une migration de CMS ordinaire. Mais la conséquence est bien réelle, et c'est la raison pour laquelle cet article cite les deux entrées intégralement : la trace primaire d'une dépréciation devrait survivre à la chose qu'elle déprécie, et ici, elle a bien failli ne pas le faire.
Ce qui a vraiment cassé
Pas des projets annexes. Les défaillances ci-dessous sont des entrées publiques de gestionnaires de tickets ; les titres, dates et états proviennent des API GitHub et drupal.org.
| Projet | Ticket | Ouvert le | Ce qu'il dit |
|---|---|---|---|
| MUI (Material UI) | #42736 | 24 juin 2024 | « [docs] Random Unsplash photo URL is no longer functional » — le modèle officiel Sign-in side livrait une image morte. Fermé trois jours plus tard. |
| Nextcloud | #115 | 17 jan. 2023 | « Migrate to Unsplash API » — l'application d'arrière-plan reposait sur des URI Source. Resté ouvert dix-huit mois, fermé le 16 juillet 2024. |
| sindresorhus/Actions | #248 | 28 mai 2024 | « Get Unsplash Image: 503 Error » — une action iOS/macOS Shortcuts, cassée deux semaines avant l'annonce du retrait. |
| Drupal — Gin Login | #3324054 | 28 nov. 2022 | « Unsplash has deprecated source.unsplash.com — this delays reCAPTCHA from loading, preventing users from logging in. » |
Relisez cette dernière ligne, car c'est celle qu'il vaut la peine d'intérioriser. Une image décorative à côté d'un formulaire de connexion — l'élément le plus manifestement non critique de la page — a dégénéré en panne d'authentification, parce qu'une requête tierce lente se trouvait sur le chemin du CAPTCHA dont le formulaire de connexion avait besoin. Personne ne l'a conçu ainsi. C'est né de l'ordre dans lequel un navigateur charge les éléments.
Votre assistant de codage n'a pas reçu le mémo
Voici la partie qui transforme une panne de 2024 en problème de 2026. source.unsplash.com
a été documenté, commenté sur des blogs, enseigné et copié pendant environ huit ans. Tout ce texte se trouve dans les données d'entraînement des modèles qui écrivent maintenant notre code de démarrage — et le texte n'expire pas. Trois décomptes :
| Mesure | Valeur au 30 août 2026 | Comment elle a été obtenue |
|---|---|---|
Fichiers contenant source.unsplash.com |
3 344 | API de recherche de code GitHub, q=source.unsplash.com (uniquement le code public indexé — un plancher, pas un total) |
| Dépôts, dans un échantillon de 100 fichiers, créés après le retrait | 12 sur 77 | Même requête, 100 résultats, dédupliqués en 77 dépôts, created_at comparé au 11 juin 2024 |
| …et dépôts de cet échantillon ayant reçu un push dans les 12 derniers mois | 20 sur 77 | Dépôts vivants, pas des archives — dont elastic/kibana, dont le fichier de démo contient encore imageUrl: 'https://source.unsplash.com/64x64/?dingo' |
Téléchargements mensuels de unsplash-source-es6 |
23 | API du registre npm — un wrapper pour un service mort, publié pour la dernière fois en 2022, encore installé |
| Publications Stack Overflow le mentionnant | 1 459 | API Stack Exchange, total /search/excerpts |
La preuve la plus directe ne se trouve pas dans du code applicatif — elle se trouve dans des prompts. Le premier résultat pour cette recherche est une bibliothèque de prompts système GPT contenant la ligne « please use unsplash API( https://source.unsplash.com/1280x720/?<PUT YOUR QUERY HERE>) ». Cette instruction est encore copiée dans de nouveaux assistants aujourd'hui. Le modèle ne vérifie pas l'URL ; on lui a dit de l'utiliser, et chaque exemple qu'il a vu allait dans ce sens.
Ainsi, l'échec du code généré a deux causes indépendantes, et corriger l'une ne corrige pas l'autre : des données d'entraînement obsolètes, et des instructions obsolètes écrites par des humains par-dessus. Dans les deux cas, le symptôme est la même famille d'images qui ne se chargent jamais :
source.unsplash.com/random/1200x800 # 503 depuis mi-2024 — ne revient jamais
images.unsplash.com/photo-… # vrai CDN, mais les ID mémorisés peuvent ne plus exister
via.placeholder.com/400 # rectangle gris, livré en production
placehold.co/800x600 # rectangle gris, volontairement
picsum.photos/800/600 # une vraie photo, sans rapport avec votre page
/placeholder.png # un fichier qui n'a jamais été ajouté au dépôt
Une note sur la deuxième ligne de cette liste : en écrivant cet article, via.placeholder.com
n'a pas non plus réussi à établir de poignée de main TLS depuis notre réseau de test, et a répondu 403
en HTTP simple. Vérifiez-le depuis votre propre réseau avant de lui faire confiance — le repli vers lequel se tournent ces outils peut avoir sa propre histoire de panne.
Seul le premier motif est cassé. Les autres sont pires, d'une manière plus subtile : ils se chargent, la mise en page paraît terminée, et personne ne remarque que la page est illustrée par quelque chose qui ne correspond à rien de particulier. Et rien de tout cela n'est propre aux images — c'est la forme générale du problème. La vision du web d'un modèle est un instantané, et les points d'accès, les options de CLI, les noms de paquets et les offres gratuites continuent de bouger après la fermeture de l'obturateur.
La table de migration
Il existe exactement trois destinations, et la manière honnête de les présenter est de préciser ce à quoi on renonce. Choisissez d'abord la colonne, puis lisez votre ligne.
| Ancienne URL Source | A. URL CDN fixepas de clé · pas d'aléa | B. API Unsplashclé · appel côté serveur | C. Votre propre proxyclé cachée · aléa retrouvé |
|---|---|---|---|
/random |
images.unsplash.com/photo-… — une photo que vous avez choisie |
GET /photos/random |
/?w=1600 |
/random/1600x900 |
…?w=1600&h=900&fit=crop |
/photos/random + paramètres Imgix sur l'URL retournée |
/?w=1600&h=900&fit=crop |
/1600x900/?apple,desk |
Aucun équivalent — choisissez une photo à la main | /photos/random?query=apple,desk |
/?query=apple,desk&w=1600 |
/featured/1600x900?nature |
Aucun équivalent | /photos/random?query=nature « featured » n'a pas de successeur |
/?query=nature&w=1600 |
/collection/67920491/1600x900 |
Aucun équivalent | /photos/random?collections=67920491 |
/?collections=67920491&w=1600 |
/user/scottwebb/1600x900 |
Aucun équivalent | /photos/random?username=scottwebb |
/?username=scottwebb&w=1600 |
/daily |
Fixez une photo, faites-la tourner à chaque build | Aucun équivalent — mettez vous-même en cache une photo aléatoire pour 24 h | Idem, avec le cache dans le proxy |
L'option A est celle que la plupart des gens veulent réellement. Si l'image était décorative — une bannière, un panneau latéral de connexion, un fond de carte — vous n'aviez jamais besoin d'une photo différente à chaque requête. Choisissez-en une, conservez l'URL CDN, et la page cesse de dépendre de quoi que ce soit d'aléatoire :
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1600&h=900&fit=crop&auto=format"
width="1600" height="900" alt="…">
# Paramètres officiellement pris en charge : w, h, crop, fit, fm, auto=format, q, dpr.
# Conservez tout paramètre ixid fourni par l'API — c'est lui qui déclenche le comptage de la vue.
L'option B est la voie officielle, et elle déplace l'appel côté serveur, car un
Client-ID dans du JavaScript front-end est un identifiant publié. Notez les deux règles qui piègent les gens : collections/topics ne peuvent pas être combinés avec
query dans la même requête, et count (max 30) change la forme de la réponse, qui devient un tableau même quand elle vaut 1.
curl "https://api.unsplash.com/photos/random?query=nature&orientation=landscape" \
-H "Authorization: Client-ID YOUR_ACCESS_KEY" \
-H "Accept-Version: v1"
# → JSON. L'image se trouve dans .urls.regular / .urls.raw (ajoutez w/h/fit vous-même).
# → X-Ratelimit-Limit: 1000 X-Ratelimit-Remaining: 999
Option C : reconstruire Source, en une quarantaine de lignes
Si ce que vous avez perdu était véritablement le comportement — une URL sans clé qui renvoie une photo différente à chaque fois, utilisable directement dans une balise <img>, dans un champ de CMS, ou dans un site statique où il n'y a pas de serveur — alors il vous faut faire tourner ce point d'accès vous-même. C'est un petit worker devant l'API, et les trois éléments qui lui permettent de survivre au contact de la production sont le cache, la vérification du référent, et la transmission du reste de la chaîne de requête au CDN.
// Cloudflare Workers. Ailleurs (Deno Deploy, Val Town…) la forme est la même,
// mais ouvrez un cache nommé avec caches.open() plutôt que caches.default.
// UNSPLASH_KEY reste côté serveur. Les appelants ne la voient jamais.
const ALLOWED = ["example.com", "www.example.com"]; // vos domaines uniquement
const API_PARAMS = ["query", "collections", "topics", "username", "orientation"];
const TTL = 60; // secondes — protège le quota horaire
const host = (value) => { try { return new URL(value).hostname; } catch { return null; } };
export default {
async fetch(req, env, ctx) {
// 0. GET uniquement : l'API Cache refuse de stocker autre chose, et un point
// d'accès image n'a de toute façon aucun autre verbe à traiter.
if (req.method !== "GET")
return new Response("Method not allowed", { status: 405 });
const url = new URL(req.url);
// 1. Seules vos propres pages peuvent l'intégrer — un point d'accès public
// de photo aléatoire sur Internet, c'est le quota de quelqu'un d'autre à brûler.
const ref = req.headers.get("referer"); // absent chez pas mal de clients légitimes
if (ref && !ALLOWED.includes(host(ref)))
return new Response("Forbidden", { status: 403 });
// 2. Cache par combinaison de paramètres, pour qu'une page avec 12 images
// coûte un appel API par minute au lieu de douze par rendu.
const cache = caches.default;
const hit = await cache.match(req);
if (hit) return hit;
// 3. Demander une photo aléatoire à l'API officielle.
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",
}});
// un 403 ici signale en général le quota horaire, pas une mauvaise clé — augmentez le TTL, ne paniquez pas
if (!r.ok) return new Response("Upstream " + r.status, { status: 502 });
const photo = await r.json();
// 4. Reconstruire l'URL de l'image : garder ixid, ajouter les paramètres de taille de l'appelant.
const img = new URL(photo.urls.raw); // .raw porte déjà 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,
// le crédit voyage avec la redirection ; les valeurs d'en-tête doivent être en ASCII, d'où l'encodage
"X-Photo-Credit": encodeURIComponent(photo.user.name + " on Unsplash"),
"X-Photo-Link": photo.links.html,
}});
ctx.waitUntil(cache.put(req, res.clone()));
return res;
},
};
Migrer une URL devient alors une opération de rechercher-remplacer, ce qui est exactement ce qui rend cette option rentable en vingt minutes :
- https://source.unsplash.com/collection/67920491/1600x900
+ https://img.example.com/?collections=67920491&w=1600&h=900&fit=crop
Deux remarques de conception, qui ont chacune coûté une mauvaise après-midi à quelqu'un avant d'être consignées. La redirection (302), plutôt que le relais des octets, vous dispense de payer la bande passante
et continue de faire compter la vue sur le CDN d'Unsplash, ce que demandent les directives. Et
la vérification du Referer est volontairement permissive quand l'en-tête est absent — de nombreux clients légitimes le suppriment — tout en bloquant bien le cas évident où votre point d'accès deviendrait l'API image gratuite de quelqu'un d'autre.
Les règles que vous héritez dès que vous utilisez l'API
Source n'avait pas de règles parce qu'il n'y avait pas de compte. L'API en a cinq qui changent la façon dont vous architecturez la chose, toutes tirées de la documentation actuelle :
- Les limites de débit sont par heure, et faibles au départ. 50 requêtes/heure
en mode démo ; 1 000/heure une fois votre application approuvée pour la production. Seuls les appels vers
api.unsplash.comcomptent — les requêtes d'image versimages.unsplash.comne comptent pas. LisezX-Ratelimit-Remainingsur chaque réponse. - Le hotlinking est obligatoire, pas simplement autorisé. Unsplash exige que les URL d'images renvoyées par l'API soient intégrées directement, afin que les vues de la photo puissent être attribuées au photographe. Copier le fichier vers votre propre CDN est la seule optimisation que vous n'avez pas le droit de faire.
- Conservez le paramètre
ixid. Redimensionner et recadrer l'URL retournée est attendu ; en retirer le paramètre qui identifie votre application ne l'est pas. - L'attribution et le suivi des téléchargements font partie du contrat — le photographe et Unsplash sont crédités, et un « téléchargement » est signalé via le point d'accès de téléchargement de la photo lorsqu'un utilisateur récupère le fichier, ce qui est un événement que vous devez déclencher vous-même.
- Les produits distribués nécessitent un enregistrement dynamique des clients. Si vous livrez un plugin, un thème ou un CMS auto-hébergé, une seule clé partagée constitue à la fois une violation des règles et un point de défaillance unique ; l'API dispose d'un mécanisme d'enregistrement prévu exactement pour ce cas.
C'est le moment d'être honnête sur le périmètre : si vous mettez de toute façon en place une API et une clé, le choix de quelle API image utiliser devient soudain ouvert, et il vaut la peine d'y consacrer cinq minutes avant d'écrire le client. Nous avons comparé les offres gratuites — quotas, règles, comportement de recherche et formes de réponse — dans la comparaison des API photo stock gratuites.
Si vous vouliez seulement un espace réservé, dites-le
Une large part de l'usage de Source n'a jamais eu de rapport avec Unsplash. Il s'agissait de « mettre quelque chose qui ressemble à une image ici pendant que je construis la mise en page ». Pour cela, des services sans clé existent toujours et sont la bonne réponse :
| Service | Clé ? | Ce que vous obtenez | Où ça s'arrête |
|---|---|---|---|
| Lorem Picsum | Non | De vraies photographies : picsum.photos/800/600, une fixe avec /id/237/… ou /seed/xxx/…, plus ?grayscale et ?blur=1..10. Son point d'accès /v2/list crédite la page Unsplash et l'auteur de chaque photo. |
Aucun ciblage de sujet. La photo n'aura aucun rapport avec votre page. |
| placehold.co | Non | Des rectangles étiquetés, à n'importe quelle taille — un vrai remplisseur de maquette, honnête. | C'est une boîte grise, et elle ressemble à une boîte grise sur une capture d'écran partagée avec un client. |
| Openverse | Non | Un catalogue sous licence ouverte avec une API publique, géré par WordPress.org. | Correspondance par mots-clés, et les licences varient selon l'élément — il faut les lire. |
La distinction qui compte : un espace réservé est temporaire par définition. Si l'image survit jusqu'en production, ce n'est plus un espace réservé — c'est une illustration que personne n'a choisie, et le lecteur le remarque.
Une seule clé au lieu de trois
Voici la partie de la migration que personne ne planifie : ceux qui quittent Source atterrissent rarement sur
une seule API. Une page a besoin d'une bannière, de deux images de section et de quelque chose pour une grille de cartes, et la réponse honnête est généralement Unsplash plus Pexels plus Pixabay — trois
inscriptions, trois schémas d'authentification, trois formes de JSON, trois modèles de pagination et trois jeux de règles d'attribution, le tout pour remplir les mêmes balises <img>. Ce travail d'intégration est la véritable facture de la disparition d'une URL sans clé, et elle arrive des semaines après la panne qui l'a causée.
Ramener tout cela à une seule intégration, c'est exactement pourquoi nous avons construit Pexafy : une seule clé sur 9 bibliothèques à licence gratuite dans un schéma unique, avec une recherche sémantique au niveau de la phrase — de sorte qu'une description complète comme « un écran de téléphone fissuré sur un bureau en bois, vu du dessus » retourne des résultats classés au lieu de rien. Deux limites, énoncées clairement, car tout cet article porte sur le fait de ne pas être surpris deux fois : cela nécessite une clé, donc cela ne restaure pas ce qu'était Source — cela appartient à la même catégorie que l'API Unsplash officielle mentionnée plus haut ; et cela couvre de la photographie sous licence gratuite, pas de l'imagerie éditoriale ou de marque.
L'élément véritablement nouveau vise les assistants évoqués plus haut : un serveur MCP à
mcp.pexafy.com/mcp permet à un modèle qui réciterait autrement une URL d'image de mémoire de chercher dans un vrai catalogue et de retourner une photo qui existe réellement, avec sa mention de crédit attachée. C'est une meilleure réponse aux URL mortes écrites par des machines que n'importe quelle règle de lint, et le raisonnement est détaillé dans
l'infrastructure de recherche d'images pour les agents IA.
Auditez votre parc en dix minutes
Quelle que soit votre destination de migration, faites d'abord cette partie — on ne peut pas corriger des URL qu'on n'a pas trouvées. Source n'est que l'exemple du jour ; les trois mêmes étapes s'appliquent à toute ressource externe que vous intégrez.
# 1. Tout dans le dépôt, y compris la documentation, les tests, les fixtures et les README.
grep -rn --binary-files=without-match \
-e "source.unsplash.com" -e "via.placeholder.com" -e "/placeholder.png" .
# 2. Tout ce que contient la base de données — c'est là que ces liens se cachent le plus longtemps, dans le corps des CMS.
psql -c "SELECT id FROM posts WHERE body LIKE '%source.unsplash.com%'"
# 3. Tout ce que le site construit demande réellement : parcourez-le et listez les échecs.
# Faites correspondre l'attribut, pas une extension de fichier — les URL d'images se terminent rarement en .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 ← ce que vous cherchez
Décidez ensuite, une bonne fois pour toutes, du coût que les images externes ont le droit de vous imposer. Quatre règles qui survivent à la prochaine fermeture de service, quel qu'en soit le responsable :
- Récupérez l'image au moment du build, pas au moment de la requête. Une image résolue pendant le build échoue en intégration continue, devant un développeur, plutôt qu'à 3 h du matin devant un utilisateur.
- Ne laissez jamais une ressource décorative bloquer un chemin critique. Ne préchargez rien d'externe
au-dessus d'un formulaire de connexion ; donnez à chaque
<img>tiers un replionerroret deswidth/heightexplicites, afin qu'un échec coûte une boîte vide, pas un décalage de mise en page ni un script bloqué. - Ajoutez la vérification à l'intégration continue. L'étape 3 ci-dessus, exécutée sur votre résultat de build, transforme « quelqu'un a fini par remarquer » en un build en échec. C'est la seule étape qui empêche la récidive.
- Budgétisez vos dépendances externes comme n'importe quelle autre. Consignez de quels hôtes vos pages ont le droit de dépendre et ce qui se passe quand chacun est hors service. Une URL pour laquelle vous n'avez pas eu besoin de vous inscrire reste une dépendance — Source a prouvé que c'est simplement une dépendance que personne ne possède.
Références & notes de bas de page
1 Chaque code de statut, chaque décompte et chaque ligne citée dans cet article ont été
tirés de leur source primaire le 30 août 2026. Les codes de statut HTTP ont été obtenus avec curl contre chaque motif d'URL ; tous ont renvoyé 503 avec
server: Heroku et un corps intégrant
herokucdn.com/error-pages/application-error.html. La résolution DNS a été confirmée le même jour (un CNAME vers un hôte herokudns.com).
2 Les deux entrées de changelog sont citées mot pour mot depuis
unsplash.com/documentation/changelog. Le libellé de la politique de dépréciation (3 semaines de préavis, en-tête Warning, et l'exemption pour les points d'accès non publiquement documentés) provient de unsplash.com/documentation, le même jour.
3 L'échec TLS a été reproduit avec
openssl s_client -connect changelog.unsplash.com:443 (tlsv1 alert internal
error). La chaîne de redirection a été suivie avec curl -L. L'écart d'archive provient de l'API CDX de la
Wayback : dernière capture 200 de changelog.unsplash.com le
20240324, première capture de unsplash.com/documentation/changelog le 20240823.
4 Titres de tickets, dates de création et de clôture lus depuis l'API REST de GitHub
(mui/material-ui#42736, nextcloud/unsplash#115,
sindresorhus/Actions#248) et depuis l'API JSON de drupal.org pour le ticket
gin_login 3324054, dont le corps signale « I get always a Heroku application
error » en novembre 2022.
5 Décomptes : API de recherche de code GitHub
(3 344 fichiers ; un échantillon de 100 résultats dédupliqué en 77 dépôts, dont 12 ont été
créés après le 11 juin 2024 et 20 avaient reçu un push dans les 12 mois précédents) ;
API de téléchargements du registre npm (unsplash-source-es6, 23 téléchargements sur les
30 jours précédents) ; /search/excerpts de Stack Exchange (1 459 publications). La recherche de code
ne couvre que les dépôts publics indexés, donc chaque chiffre constitue un plancher.
Sources primaires : changelog de l'API Unsplash · documentation de l'API Unsplash · directive d'attribution Unsplash · statut d'Unsplash · MUI #42736 · Nextcloud #115 · sindresorhus/Actions #248 · Drupal Gin Login #3324054 · Lorem Picsum · Openverse · documentation API & MCP de Pexafy.
Foire aux questions
source.unsplash.com est-il en panne, ou a-t-il été arrêté définitivement ?
/random, /1600x900/?query, /collection/…, /daily) renvoie désormais un HTTP 503 avec la page générique Application Error de Heroku. Le nom d'hôte se résout toujours, donc l'échec apparaît comme une image cassée plutôt qu'une erreur réseau.Quel est le remplacement direct de source.unsplash.com/random ?
images.unsplash.com/photo-…?w=1600&h=900&fit=crop — ne nécessite aucune clé mais renvoie toujours la même photo, ce qui correspond en réalité à la plupart des usages décoratifs. L'API officielle, GET https://api.unsplash.com/photos/random avec un en-tête Authorization: Client-ID, restaure l'aléatoire mais doit être appelée côté serveur. Un petit proxy que vous hébergez vous-même devant ce endpoint est la seule option qui redonne une URL sans clé, utilisable directement dans une balise <img>.Pourquoi les outils de codage IA génèrent-ils encore des URL source.unsplash.com en 2026 ?
Puis-je encore obtenir une photo Unsplash aléatoire sans clé API ?
/photos/random, qui nécessite un Client-ID. Vos deux routes sans clé sont un proxy que vous hébergez vous-même, où la clé reste côté serveur et l'URL publique ressemble à l'ancienne, ou un service tiers de type placeholder comme Lorem Picsum (picsum.photos/800/600), qui sert de vraies photographies sans clé mais sans aucun ciblage de sujet.L'API Unsplash permet-elle de télécharger et d'auto-héberger les images ?
ixid lors du redimensionnement ou du recadrage de l'URL, créditer le photographe et Unsplash, et déclencher le endpoint de téléchargement de la photo lorsqu'un utilisateur récupère le fichier. Copier les fichiers sur votre propre CDN est la seule optimisation que vous n'êtes pas libre de faire.Pourquoi l'avis de dépréciation de 2021 n'a-t-il pas protégé les utilisateurs existants ?
Warning — s'applique aux champs et endpoints documentés publiquement, et ce même paragraphe précise que tout ce qui n'est pas documenté peut changer sans avertissement. Source n'a jamais été un endpoint documenté de l'API, il se trouvait donc hors du champ de la politique censée le protéger.Comment retrouver toutes les URL source.unsplash.com mortes dans mon projet ?
grep -rn "source.unsplash.com" . — car ces URL survivent le plus longtemps dans le code d'exemple. Interrogez la base de données, car c'est dans le corps des articles du CMS qu'elles se cachent (WHERE body LIKE '%source.unsplash.com%'). Puis parcourez votre build final : extrayez chaque URL d'image et interrogez-la, en listant tout ce qui ne renvoie pas 200. Ajoutez ce dernier passage à votre CI, pour qu'un asset tiers mort fasse échouer un build plutôt qu'une page.