source.unsplash.com উধাও: পোস্ট-মর্টেম এবং একে বদলানোর প্রতিটি উপায়
২০২১ সালে “existing uses will continue to work” বলে ডিপ্রিকেট করা হয়েছিল, ২০২৪ সালের জুনে সম্পূর্ণ বন্ধ করে দেওয়া হয়, এবং আজও নতুন কোডে লেখা হচ্ছে। পরিমিত পোস্ট-মর্টেম — এবং তিনটি বিকল্প, সেই প্রক্সি সহ যা key ছাড়াই র্যান্ডমনেস ফিরিয়ে দেয়।
একটি স্টক ফটো সার্ভিস একটি সাবডোমেইন বন্ধ করে দিয়েছে, আর দুই বছর পরেও তা ডকুমেন্টেশন সাইট, লগইন স্ক্রিন, কোর্সের এক্সারসাইজ এবং সদ্য তৈরি করা কোড ভেঙে দিচ্ছে। এটি একটি URL-এর post-mortem — এটি কী করত, কী একে মেরে ফেলল, আর তার জায়গায় কী বসাতে হবে — সেই সাথে গল্পের অদ্ভুততর অংশটির একটি পরিমিত পর্যালোচনা: যে মেশিনগুলো আমাদের কোড লেখে তারা লক্ষ্যই করেনি যে এটি চলে গেছে।
HTTP রেসপন্সগুলো, শব্দে শব্দে উদ্ধৃত দুটি changelog এন্ট্রি, API সীমাগুলো, যেসব প্রজেক্ট ভেঙে গেছে তাদের ইস্যু ট্র্যাকার এবং GitHub, npm ও Stack Overflow থেকে নেওয়া সংখ্যাগুলো — সবই প্রাথমিক সূত্র থেকে নেওয়া, প্রতিটির পদ্ধতি ফুটনোটে দেওয়া আছে। শুধু সমাধান চাইলে মাইগ্রেশন টেবিলে চলে যান।
আজ আপনি যা পাবেন, যদি এখনও এটি রিকোয়েস্ট করেন
একটি কমান্ড, কোনো key ছাড়াই, যেকোনো মেশিন থেকে reproduce করা যায়:
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
# বডি: herokucdn.com/error-pages/application-error.html নির্দেশ করা একটি iframe
এখানে কোনোটাই DNS ব্যর্থতা নয়। source.unsplash.com এখনও resolve হয় — এটি একটি herokudns.com হোস্টের CNAME — তাই রিকোয়েস্টের উত্তর দেওয়া হয়, শুধু কোনো অ্যাপ্লিকেশন দ্বারা নয়। এই বিস্তারিতটি শোনার চেয়েও বেশি গুরুত্বপূর্ণ: একটি HTML বডিসহ দ্রুত 503 পাওয়া ব্রাউজার একটি ভাঙা ইমেজ প্লেসহোল্ডার রেন্ডার করে, এবং response.ok পড়ে এমন যেকোনো কোড বা আপনি কখনও না লেখা একটি onerror হ্যান্ডলার সেই ব্যর্থতার পথ নেয় যা আপনি কখনও টেস্ট করেননি।
| URL প্যাটার্ন | আগে যা রিটার্ন করত | আজ |
|---|---|---|
source.unsplash.com/random | একটি র্যান্ডম ফটো, যেকোনো সাইজে | 503 |
source.unsplash.com/random/1600x900 | একটি র্যান্ডম ফটো, সাইজ অনুযায়ী ক্রপ করা | 503 |
source.unsplash.com/1600x900/?apple,desk | সার্চ টার্মের সাথে মিলযুক্ত একটি র্যান্ডম ফটো | 503 |
source.unsplash.com/featured/1600x900?nature | একটি র্যান্ডম featured ফটো | 503 |
source.unsplash.com/collection/190727/800x600 | একটি কালেকশন থেকে র্যান্ডম ফটো | 503 |
source.unsplash.com/user/scottwebb/1600x900 | একজন ফটোগ্রাফারের একটি র্যান্ডম ফটো | 503 |
source.unsplash.com/daily | দিনের ফটো | 503 |
curl -o /dev/null -w "%{http_code}" দিয়ে আলাদাভাবে চেক করা হয়েছে। ঘোষণা অনুযায়ী সার্চ ফিচারটি প্রথমে বন্ধ করা হয়েছিল; আজ পুরো অ্যাপ্লিকেশনটিই বন্ধ, তাই এই পার্থক্যটি আর নেই।
“deprecated” এবং “off”-এর মধ্যে তিন বছর
উভয় ঘোষণাই এখনও এক জায়গায় পড়া যায়, unsplash.com/documentation/changelog-এ। সম্পূর্ণভাবে উদ্ধৃত, কারণ শব্দচয়নই পুরো গল্প বলে দেয়:
25 নভেম্বর 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 জুন 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.”
এগুলো ক্রমানুসারে পড়লে ব্যর্থতার ধরনটি স্পষ্ট হয়ে যায়। 2021 সালের নোটিসে একটি প্রতিশ্রুতি ছিল (existing uses will continue to work) এবং কোনো তারিখ ছিল না। যে ডেভেলপার এটি 2021 সালে পড়েছিলেন তার কাছে কার্যকর কোড অপরিবর্তিত রাখার সব কারণ ছিল; যে ডেভেলপার 2022 সালে যোগ দিয়েছিলেন তিনি এটি একেবারেই পড়েননি। 2024 সালের নোটিসটি “আসছে সপ্তাহগুলোতে” বলেছিল, তিন বছর পরে, এমন একটি পেজে যা কেউ বুকমার্কই করেননি।
Unsplash একটি deprecation policy প্রকাশ করে, এবং এটি একটি যুক্তিসঙ্গত নীতি — ডকুমেন্টেশনে বলা হয়েছে যে পাবলিকলি ডকুমেন্টেড ফিল্ড এবং এন্ডপয়েন্টের জন্য, পরিবর্তনগুলো কমপক্ষে 3 সপ্তাহ আগে changelog-এ ঘোষণা করা হয়, এবং deprecation পিরিয়ডে এন্ডপয়েন্টগুলো একটি Warning হেডার রিটার্ন করে। একই অনুচ্ছেদে সেই বাক্যটিও আছে যা ব্যাখ্যা করে কেন এর কোনোটিই Source-কে সুরক্ষা দেয়নি: “For any non-publicly documented fields or endpoints, we may make changes to these with no warning.” Source কখনোই ডকুমেন্টেড API-এর একটি এন্ডপয়েন্ট ছিল না। এটি সেই নীতির বাইরে ছিল যা একে কভার করত।
- ব্যবহারিক শিক্ষাটি “Unsplash অসতর্ক ছিল” নয়। শিক্ষাটি হলো — কোনো ডকুমেন্টেশন না পড়েই যে URL ব্যবহার করা যায়, সেই URL-এর deprecation policyও আপনি পড়েননি।
- ঘোষণার আগেই ভাঙন শুরু হয়েছিল। 28 নভেম্বর 2022 তারিখে দাখিল করা একটি Drupal ইস্যুতে ইতিমধ্যেই রিপোর্ট করা হয়েছিল “I get always a Heroku application error”, sunset-এর আঠারো মাস আগে। বিরতিহীনভাবে ব্যর্থতাই হলো এই সার্ভিসগুলোর মৃত্যুর ধরন: ধীরে ধীরে, তারপর এমন একটি ঘোষণায় যা আপনি কখনো দেখেনই না।
ঘোষণাটি আউটেজের চেয়েও খুঁজে পাওয়া কঠিন
2021 সালের deprecation প্রকাশিত হয়েছিল changelog.unsplash.com-এ, এবং এই URLটিই সেই সময়কার প্রতিটি বাগ রিপোর্ট লিংক করে — উপরের Drupal ইস্যুসহ। তিনটি পরিমাপ:
- HTTPS এন্ডপয়েন্টটি ভাঙা।
openssl s_client -connect changelog.unsplash.com:443রিটার্ন করেtlsv1 alert internal error— কোনো সার্টিফিকেট উপস্থাপনের আগেই handshake ব্যর্থ হয়। তাই 2021-যুগের প্রতিটি লিংক, যাhttps://ছিল, ব্রাউজারে মৃত। - সাধারণ HTTP-এর মাধ্যমে এটি রিডাইরেক্ট করে, কিন্তু উপযোগীভাবে নয়।
http://changelog.unsplash.com/deprecations/2021/11/25/source-deprecation.htmlফলো করলে, দুই hop পরে, এটিunsplash.com/@documentation/changelog/deprecations/2021/11/25/source-deprecation/html-এ একটি400-এ গিয়ে শেষ হয় — পাথটি সাইটের ইউজারনেম রুট দ্বারা গ্রাস করা হয়েছে। - আর্কাইভে ঠিক sunset-এর জায়গায় একটি ফাঁক আছে। Wayback Machine-এর পুরনো changelog-এর শেষ সফল ক্যাপচার হলো 24 মার্চ 2024; নতুনটির প্রথম ক্যাপচার হলো 23 আগস্ট 2024। sunset ঘোষিত হয়েছিল 11 জুন 2024-এ — সেই পাঁচ মাসের ফাঁকের মধ্যেই।
এর কোনোটিই কোনো ষড়যন্ত্র নয়; এটি একটি সাধারণ CMS মাইগ্রেশন। কিন্তু পরিণতিটি বাস্তব, এবং এই কারণেই এই আর্টিকেলটি উভয় এন্ট্রিকেই সম্পূর্ণভাবে উদ্ধৃত করে: একটি deprecation-এর প্রাথমিক রেকর্ডের যেই জিনিসটিকে deprecate করা হয়েছে তার চেয়ে বেশি দিন টিকে থাকা উচিত, আর এক্ষেত্রে সেটা প্রায় হয়নি।
আসলে কী ভেঙেছিল
সাইড প্রজেক্ট নয়। নিচের ব্যর্থতাগুলো পাবলিক ইস্যু-ট্র্যাকার এন্ট্রি; শিরোনাম, তারিখ এবং স্ট্যাটাস GitHub এবং drupal.org API থেকে নেওয়া।
| প্রজেক্ট | ইস্যু | খোলা হয়েছিল | এতে কী বলা আছে |
|---|---|---|---|
| MUI (Material UI) | #42736 | 24 জুন 2024 | “[docs] Random Unsplash photo URL is no longer functional” — অফিসিয়াল Sign-in side টেমপ্লেটটি একটি মৃত ইমেজ শিপ করেছিল। তিন দিন পরে বন্ধ করা হয়। |
| Nextcloud | #115 | 17 জানুয়ারি 2023 | “Migrate to Unsplash API” — ব্যাকগ্রাউন্ড অ্যাপটি Source URI-এর উপর তৈরি হয়েছিল। আঠারো মাস খোলা ছিল, 16 জুলাই 2024-এ বন্ধ হয়। |
| sindresorhus/Actions | #248 | 28 মে 2024 | “Get Unsplash Image: 503 Error” — একটি iOS/macOS Shortcuts অ্যাকশন, sunset ঘোষণার দুই সপ্তাহ আগেই ভাঙা। |
| Drupal — Gin Login | #3324054 | 28 নভেম্বর 2022 | “Unsplash has deprecated source.unsplash.com — this delays reCAPTCHA from loading, preventing users from logging in.” |
শেষ সারিটি আবার পড়ুন, কারণ এটিই মনে রাখার মতো। একটি লগইন ফর্মের পাশের সাজসজ্জামূলক ইমেজ — পেজের সবচেয়ে স্পষ্টভাবে অ-গুরুত্বপূর্ণ অ্যাসেট — একটি অথেন্টিকেশন আউটেজে পরিণত হয়ে গেল, কারণ একটি ধীরগতির থার্ড-পার্টি রিকোয়েস্ট লগইন ফর্মের প্রয়োজনীয় CAPTCHA-এর সামনে বসে ছিল। কেউ এভাবে লেখেনি। এটি ব্রাউজার যেভাবে জিনিস লোড করে সেই ক্রম থেকেই উদ্ভূত হয়েছিল।
আপনার কোডিং অ্যাসিস্ট্যান্ট বার্তা পায়নি
এখানেই সেই অংশ যা 2024 সালের একটি আউটেজকে 2026 সালের একটি সমস্যায় পরিণত করে। source.unsplash.com প্রায় আট বছর ধরে ডকুমেন্টেড, ব্লগ-লিখিত, শেখানো এবং কপি করা হয়েছিল। এই সমস্ত টেক্সট এখন সেই মডেলগুলোর ট্রেনিং ডেটায় আছে যেগুলো এখন আমাদের স্টার্টার কোড লেখে — আর টেক্সটের কোনো এক্সপায়ারি হয় না। তিনটি গণনা:
| পরিমাপ | 30 আগস্ট 2026-এ মান | যেভাবে নেওয়া হয়েছে |
|---|---|---|
source.unsplash.com ধারণকারী ফাইল |
3,344 | GitHub কোড সার্চ API, q=source.unsplash.com (শুধু ইনডেক্স করা পাবলিক কোড — একটি সর্বনিম্ন সীমা, মোট নয়) |
| 100-ফাইল স্যাম্পেলে sunset-এর পরে তৈরি হওয়া রিপোজিটরি | 77-এর মধ্যে 12 | একই কোয়েরি, 100টি ফলাফল, 77টি রিপোজিটরিতে ডিডুপ্লিকেট, created_at-কে 11 জুন 2024-এর সাথে তুলনা করা |
| …এবং সেই স্যাম্পেলে গত 12 মাসে পুশ করা রিপোজিটরি | 77-এর মধ্যে 20 | লাইভ রিপোজিটরি, আর্কাইভ নয় — এর মধ্যে আছে elastic/kibana, যার ডেমো ফাইলে এখনও লেখা আছে imageUrl: 'https://source.unsplash.com/64x64/?dingo' |
unsplash-source-es6-এর মাসিক ডাউনলোড |
23 | npm রেজিস্ট্রি API — একটি মৃত সার্ভিসের জন্য একটি র্যাপার, সর্বশেষ 2022 সালে প্রকাশিত, এখনও ইনস্টল হচ্ছে |
| এটির উল্লেখকারী Stack Overflow পোস্ট | 1,459 | Stack Exchange API, /search/excerpts মোট |
সবচেয়ে সরাসরি প্রমাণটি অ্যাপ্লিকেশন কোডেই নেই — এটি প্রম্পটে আছে। সেই সার্চের শীর্ষ ফলাফল হলো GPT সিস্টেম প্রম্পটের একটি লাইব্রেরি যাতে এই লাইনটি আছে “please use unsplash API( https://source.unsplash.com/1280x720/?<PUT YOUR QUERY HERE>”। এই নির্দেশনাটি আজও নতুন অ্যাসিস্ট্যান্টগুলোতে কপি করা হচ্ছে। মডেলটি URLটি যাচাই করে না; এটিকে ব্যবহার করতে বলা হয়েছিল, আর এটি যত উদাহরণ দেখেছে সবই একমত ছিল।
তাই জেনারেট করা কোডের ব্যর্থতার দুটি স্বতন্ত্র কারণ আছে, আর একটি ঠিক করলে অন্যটি ঠিক হয় না: পুরনো ট্রেনিং ডেটা, এবং তার উপর মানুষদের লেখা পুরনো নির্দেশনা। যেভাবেই হোক, লক্ষণটি একই ধরনের ইমেজ যা কখনো লোড হয় না:
source.unsplash.com/random/1200x800 # 2024-এর মাঝামাঝি থেকে 503 — আর ফেরে না
images.unsplash.com/photo-… # আসল CDN, কিন্তু মুখস্থ করা ID-গুলো নাও থাকতে পারে
via.placeholder.com/400 # ধূসর আয়তক্ষেত্র, প্রোডাকশনে চলে গেছে
placehold.co/800x600 # ধূসর আয়তক্ষেত্র, ইচ্ছাকৃতভাবে
picsum.photos/800/600 # একটি আসল ফটো, আপনার পেজের সাথে অসম্পর্কিত
/placeholder.png # এমন একটি ফাইল যা কখনো রিপোতে যোগ করা হয়নি
সেই তালিকার দ্বিতীয় লাইনে একটি ফুটনোট: এটি লেখার সময়, via.placeholder.com আমাদের টেস্ট নেটওয়ার্ক থেকেও একটি TLS handshake সম্পূর্ণ করেনি, এবং সাধারণ HTTP-এর মাধ্যমে 403 দিয়ে উত্তর দিয়েছে। বিশ্বাস করার আগে নিজের নেটওয়ার্ক থেকে চেক করুন — এই টুলগুলো যেই fallback-এ হাত বাড়ায় তারও নিজের একটি আউটেজের গল্প থাকতে পারে।
শুধু প্রথমটিই ভাঙা। বাকিগুলো আরও সূক্ষ্মভাবে খারাপ: এগুলো লোড হয়, লেআউট সম্পূর্ণ দেখায়, আর কেউ লক্ষ্যই করে না যে পেজটি নির্দিষ্ট কিছু নয় এমন কিছু দিয়ে চিত্রিত। আর এসবের কিছুই ইমেজের জন্য নির্দিষ্ট নয় — এটি সমস্যাটির সাধারণ আকার। একটি মডেলের ওয়েবের ছবি একটি স্ন্যাপশট, আর এন্ডপয়েন্ট, CLI ফ্ল্যাগ, প্যাকেজ নাম এবং ফ্রি টিয়ার শাটার বন্ধ হওয়ার পরও নড়াচড়া করতে থাকে।
মাইগ্রেশন টেবিল
এখানে ঠিক তিনটি গন্তব্য আছে, আর এগুলো উপস্থাপনের সৎ উপায় হলো আপনি কী ছেড়ে দিচ্ছেন সেটার ভিত্তিতে। প্রথমে কলামটি বেছে নিন, তারপর আপনার সারি পড়ুন।
| পুরনো Source URL | A. ফিক্সড CDN URLকোনো key নেই · কোনো র্যান্ডমনেস নেই | B. Unsplash APIkey · সার্ভার-সাইড কল | C. আপনার নিজের প্রক্সিkey লুকানো · র্যান্ডমনেস ফিরে পাওয়া |
|---|---|---|---|
/random |
images.unsplash.com/photo-… — আপনার বেছে নেওয়া একটি ফটো |
GET /photos/random |
/?w=1600 |
/random/1600x900 |
…?w=1600&h=900&fit=crop |
/photos/random + রিটার্ন করা URL-এ Imgix প্যারামিটার |
/?w=1600&h=900&fit=crop |
/1600x900/?apple,desk |
কোনো সমতুল্য নেই — নিজে হাতে একটি ফটো বেছে নিন | /photos/random?query=apple,desk |
/?query=apple,desk&w=1600 |
/featured/1600x900?nature |
কোনো সমতুল্য নেই | /photos/random?query=nature “featured”-এর কোনো উত্তরসূরি নেই |
/?query=nature&w=1600 |
/collection/67920491/1600x900 |
কোনো সমতুল্য নেই | /photos/random?collections=67920491 |
/?collections=67920491&w=1600 |
/user/scottwebb/1600x900 |
কোনো সমতুল্য নেই | /photos/random?username=scottwebb |
/?username=scottwebb&w=1600 |
/daily |
একটি ফটো পিন করুন, আপনার বিল্ডে এটি ঘোরান | কোনো সমতুল্য নেই — নিজে 24 ঘণ্টার জন্য একটি র্যান্ডম ফটো ক্যাশ করুন | একই, ক্যাশটি প্রক্সিতে |
অপশন A হলো যেটি বেশিরভাগ মানুষ আসলে চায়। যদি ইমেজটি সাজসজ্জামূলক হয় — একটি হিরো, লগইন সাইড প্যানেল, একটি কার্ড ব্যাকগ্রাউন্ড — তাহলে আপনার কখনোই প্রতিটি রিকোয়েস্টে ভিন্ন ফটোর প্রয়োজন ছিল না। একটি বেছে নিন, CDN URL রাখুন, আর পেজটি কোনো র্যান্ডম জিনিসের উপর নির্ভর করা বন্ধ করে দেবে:
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1600&h=900&fit=crop&auto=format"
width="1600" height="900" alt="…">
# অফিসিয়ালি সমর্থিত প্যারামিটার: w, h, crop, fit, fm, auto=format, q, dpr।
# API আপনাকে যেই ixid প্যারামিটার দিয়েছিল সেটি রাখুন — এটিই ভিউ রিপোর্ট করে।
অপশন B হলো অফিসিয়াল পথ, আর এটি কলটিকে সার্ভার-সাইডে নিয়ে যায়, কারণ ফ্রন্ট-এন্ড JavaScript-এ একটি Client-ID একটি প্রকাশিত ক্রেডেনশিয়াল। দুটি নিয়ম লক্ষ্য করুন যা মানুষকে বিভ্রান্ত করে: collections/topics একই রিকোয়েস্টে query-এর সাথে মিলিত করা যায় না, আর count (সর্বোচ্চ 30) রেসপন্সের আকৃতিকে একটি অ্যারেতে পরিবর্তন করে দেয় যদিও এটি 1 হয়।
curl "https://api.unsplash.com/photos/random?query=nature&orientation=landscape" \
-H "Authorization: Client-ID YOUR_ACCESS_KEY" \
-H "Accept-Version: v1"
# → JSON। ইমেজটি থাকে .urls.regular / .urls.raw-এ (w/h/fit নিজে যোগ করুন)।
# → X-Ratelimit-Limit: 1000 X-Ratelimit-Remaining: 999
অপশন C: Source-কে পুনর্নির্মাণ করুন, প্রায় চল্লিশ লাইনে
আপনি যদি আসলে আচরণটি হারিয়ে থাকেন — একটি key-বিহীন URL যা প্রতিবার একটি ভিন্ন ফটো রিটার্ন করে, সরাসরি একটি <img> ট্যাগ থেকে, একটি CMS ফিল্ডে, অথবা একটি স্ট্যাটিক সাইটে যেখানে কোনো সার্ভার নেই — তাহলে আপনাকে সেই এন্ডপয়েন্টটি নিজেই চালাতে হবে। এটি API-এর সামনে একটি ছোট worker, আর তিনটি জিনিস যা এটিকে প্রোডাকশনের বাস্তবতার সাথে টিকিয়ে রাখে তা হলো ক্যাশ, রেফারার চেক, এবং বাকি কোয়েরি স্ট্রিং CDN-এ পাস থ্রু করা।
// Cloudflare Workers। অন্য কোথাও (Deno Deploy, Val Town…) আকৃতিটি একই,
// কিন্তু caches.default-এর বদলে caches.open() দিয়ে একটি নামযুক্ত ক্যাশ খুলুন।
// UNSPLASH_KEY সার্ভার-সাইডেই থাকে। কলাররা কখনো এটি দেখে না।
const ALLOWED = ["example.com", "www.example.com"]; // শুধু আপনার ডোমেইন
const API_PARAMS = ["query", "collections", "topics", "username", "orientation"];
const TTL = 60; // সেকেন্ড — ঘণ্টার কোটা রক্ষা করে
const host = (value) => { try { return new URL(value).hostname; } catch { return null; } };
export default {
async fetch(req, env, ctx) {
// 0. শুধু GET: Cache API অন্য কিছু সংরক্ষণ করতে অস্বীকার করে, আর একটি ইমেজ
// এন্ডপয়েন্টের উত্তর দেওয়ার মতো অন্য কোনো verb নেই।
if (req.method !== "GET")
return new Response("Method not allowed", { status: 405 });
const url = new URL(req.url);
// 1. শুধু আপনার নিজের পেজগুলোই এটি এমবেড করতে পারবে — খোলা ইন্টারনেটে একটি
// পাবলিক র্যান্ডম-ফটো এন্ডপয়েন্ট মানে অন্য কারো rate limit খরচ করা।
const ref = req.headers.get("referer"); // অনেক বৈধ ক্লায়েন্টেই অনুপস্থিত
if (ref && !ALLOWED.includes(host(ref)))
return new Response("Forbidden", { status: 403 });
// 2. প্যারামিটার কম্বিনেশন অনুযায়ী ক্যাশ করুন, যাতে 12টি ইমেজওয়ালা একটি পেজ
// প্রতি রেন্ডারে বারোটির বদলে প্রতি মিনিটে একটি API কল খরচ করে।
const cache = caches.default;
const hit = await cache.match(req);
if (hit) return hit;
// 3. অফিসিয়াল API-এর কাছে একটি র্যান্ডম ফটো চান।
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 সাধারণত ঘণ্টার কোটা বোঝায়, খারাপ key নয় — TTL বাড়ান, আতঙ্কিত হবেন না।
if (!r.ok) return new Response("Upstream " + r.status, { status: 502 });
const photo = await r.json();
// 4. ইমেজ URL পুনর্নির্মাণ করুন: ixid রাখুন, কলারের সাইজিং প্যারামিটার যোগ করুন।
const img = new URL(photo.urls.raw); // .raw-এ ইতিমধ্যে 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,
// ক্রেডিট রিডাইরেক্টের সাথে চলে; হেডার ভ্যালু অবশ্যই ASCII হতে হবে, তাই encoding।
"X-Photo-Credit": encodeURIComponent(photo.user.name + " on Unsplash"),
"X-Photo-Link": photo.links.html,
}});
ctx.waitUntil(cache.put(req, res.clone()));
return res;
},
};
তারপর একটি URL মাইগ্রেট করা হয়ে যায় একটি search-and-replace, আর এটাই এই অপশনটিকে বিশ মিনিটের মূল্যবান করে তোলে:
- https://source.unsplash.com/collection/67920491/1600x900
+ https://img.example.com/?collections=67920491&w=1600&h=900&fit=crop
দুটি ডিজাইন নোট, যেগুলো লেখা হওয়ার আগে কারো একটি খারাপ বিকেল খরচ করিয়েছিল। বাইট প্রক্সি করার বদলে রিডাইরেক্ট (302) ব্যবহার করলে ব্যান্ডউইথের ঝামেলা থেকে বাঁচা যায় এবং Unsplash-এর CDN-এ ভিউ গণনা হতে থাকে, যা গাইডলাইন অনুযায়ী প্রয়োজনীয়। আর হেডার অনুপস্থিত থাকলে Referer চেকটি ইচ্ছাকৃতভাবে অনুমতিশীল — অনেক বৈধ ক্লায়েন্টই এটি সরিয়ে দেয় — একই সাথে সেই স্পষ্ট ক্ষেত্রকেও থামায় যেখানে আপনার এন্ডপয়েন্ট অন্য কারো ফ্রি ইমেজ API হয়ে ওঠে।
API ব্যবহার করার মুহূর্তেই যেই নিয়মগুলো আপনি উত্তরাধিকারসূত্রে পান
Source-এর কোনো নিয়ম ছিল না কারণ এর কোনো অ্যাকাউন্ট ছিল না। API-এর পাঁচটি নিয়ম আছে যা আপনার আর্কিটেকচার পরিবর্তন করে দেয়, সবগুলোই বর্তমান ডকুমেন্টেশন থেকে:
- Rate limit প্রতি ঘণ্টায়, আর শুরুতে ছোট। ডেমো মোডে 50 রিকোয়েস্ট/ঘণ্টা;
আপনার অ্যাপ্লিকেশন প্রোডাকশনের জন্য অনুমোদিত হওয়ার পর 1,000/ঘণ্টা। শুধু
api.unsplash.com-এ করা কলগুলো গণনা করা হয় —images.unsplash.com-এ করা ইমেজ রিকোয়েস্টগুলো গণনা করা হয় না। প্রতিটি রেসপন্সেX-Ratelimit-Remainingপড়ুন। - Hotlinking বাধ্যতামূলক, শুধু অনুমোদিত নয়। Unsplash-এর প্রয়োজন যে API রিটার্ন করা ইমেজ URLগুলো সরাসরি এমবেড করা হোক, যাতে ফটো ভিউ ফটোগ্রাফারের নামে গণনা করা যায়। ফাইলটিকে আপনার নিজের CDN-এ মিরর করা একটি অপ্টিমাইজেশন যা আপনি করতে পারবেন না।
ixidপ্যারামিটার রাখুন। রিটার্ন করা URL রিসাইজ ও ক্রপ করা প্রত্যাশিত; আপনার অ্যাপ্লিকেশনকে চিহ্নিত করা প্যারামিটারটি সরিয়ে দেওয়া নয়।- অ্যাট্রিবিউশন এবং ডাউনলোড ট্র্যাকিং এই চুক্তির অংশ — ফটোগ্রাফার ও Unsplash উভয়েই ক্রেডিট পায়, আর একজন ব্যবহারকারী ফাইলটি নিলে ফটোর ডাউনলোড এন্ডপয়েন্টের মাধ্যমে একটি “download” রিপোর্ট করা হয়, যা একটি ইভেন্ট যা আপনাকে নিজেই ফায়ার করতে হবে।
- বিতরণকৃত পণ্যগুলোর জন্য ডাইনামিক ক্লায়েন্ট রেজিস্ট্রেশন প্রয়োজন। আপনি যদি একটি প্লাগইন, একটি থিম বা একটি সেলফ-হোস্টেড CMS শিপ করেন, তাহলে একটি শেয়ার করা key নীতি লঙ্ঘন এবং একটি একক ব্যর্থতার বিন্দু — উভয়ই; API-এর এই ক্ষেত্রের জন্য একটি রেজিস্ট্রেশন ফ্লো আছে।
এটাই সৎ থাকার মুহূর্ত পরিধি নিয়ে: আপনি যদি যাইহোক একটি API এবং একটি key দিয়ে কাজ করছেন, তাহলে কোন ইমেজ API সেই পছন্দটি হঠাৎ খোলা, আর ক্লায়েন্ট লেখার আগে পাঁচ মিনিট এতে ব্যয় করা মূল্যবান। আমরা ফ্রি অপশনগুলো তুলনা করেছি — কোটা, নিয়ম, সার্চ আচরণ এবং রেসপন্স আকৃতি — ফ্রি স্টক ফটো API তুলনা-তে।
আপনি যদি শুধু একটি প্লেসহোল্ডার চেয়ে থাকেন, তবে সেটাই বলুন
Source-এর ব্যবহারের একটি বড় অংশ কখনোই Unsplash সম্পর্কে ছিল না। এটি ছিল “আমি লেআউট তৈরি করা পর্যন্ত এখানে ইমেজ-আকৃতির কিছু রাখো”। এর জন্য, key-বিহীন সার্ভিস এখনও বিদ্যমান আছে এবং তাই সঠিক উত্তর:
| সার্ভিস | Key? | আপনি যা পাবেন | এটি কোথায় থেমে যায় |
|---|---|---|---|
| Lorem Picsum | না | আসল ফটোগ্রাফ: picsum.photos/800/600, একটি স্থির ফটো /id/237/… অথবা /seed/xxx/… দিয়ে, সেই সাথে ?grayscale এবং ?blur=1..10। এর /v2/list এন্ডপয়েন্ট প্রতিটি ফটোর Unsplash পেজ ও লেখককে ক্রেডিট দেয়। |
বিষয়-ভিত্তিক টার্গেটিং একেবারেই নেই। ফটোটি আপনার পেজের সাথে সম্পর্কিত হবে না। |
| placehold.co | না | যেকোনো সাইজে লেবেলযুক্ত আয়তক্ষেত্র — সৎ ওয়্যারফ্রেম ফিলার। | এটি একটি ধূসর বাক্স, আর ক্লায়েন্টের সাথে শেয়ার করা স্ক্রিনশটে এমনটাই দেখায়। |
| Openverse | না | WordPress.org দ্বারা পরিচালিত একটি পাবলিক API সহ একটি উন্মুক্তভাবে লাইসেন্সপ্রাপ্ত ক্যাটালগ। | কীওয়ার্ড ম্যাচিং, আর লাইসেন্স প্রতিটি আইটেমে ভিন্ন — আপনাকে সেগুলো পড়তে হবে। |
যেই পার্থক্যটি গুরুত্বপূর্ণ: একটি প্লেসহোল্ডার সংজ্ঞা অনুযায়ী সাময়িক। যদি ইমেজটি প্রোডাকশন পর্যন্ত টিকে থাকে, তাহলে এটি আর প্লেসহোল্ডার নয় — এটি এমন একটি ইলাস্ট্রেশন যা কেউ বেছে নেয়নি, আর পাঠক তা বুঝতে পারে।
তিনের বদলে একটি key
এখানেই মাইগ্রেশনের সেই অংশ যা কেউ পরিকল্পনা করে না: Source ছেড়ে যাওয়া মানুষ খুব কমই একটি API-তে পৌঁছায়। একটি পেজে একটি হিরো, দুটি সেকশন ইমেজ এবং একটি কার্ড গ্রিডের জন্য কিছু প্রয়োজন, আর সৎ উত্তরটি সাধারণত হয় Unsplash এবং Pexels এবং Pixabay — তিনটি রেজিস্ট্রেশন, তিনটি auth স্কিম, তিনটি JSON আকৃতি, তিনটি পেজিনেশন মডেল এবং তিনটি অ্যাট্রিবিউশন নিয়ম, সবই একই <img> ট্যাগ পূরণ করতে। সেই ইন্টিগ্রেশনের কাজটাই একটি key-বিহীন URL চলে যাওয়ার আসল বিল, আর এটি সেই আউটেজের সপ্তাহখানেক পরে এসে পড়ে যা এর কারণ ছিল।
এই পুরোটাকে একটি ইন্টিগ্রেশনে গুটিয়ে ফেলার জন্যই আমরা Pexafy তৈরি করেছি: একটি স্কিমায় 9টি ফ্রি-লাইসেন্স লাইব্রেরির উপর একটি একক key, বাক্য-স্তরের সিমান্টিক সার্চসহ — তাই “a cracked phone screen on a wooden desk, shot from above”-এর মতো একটি সম্পূর্ণ বর্ণনা কিছুই না দেওয়ার বদলে র্যাংক করা ফলাফল দেয়। দুটি সীমাবদ্ধতা, স্পষ্টভাবে বলা হচ্ছে, কারণ এই পুরো আর্টিকেলটাই দুইবার অবাক না হওয়ার ব্যাপারে: এর একটি key প্রয়োজন, তাই এটি Source যা ছিল তা পুনরুদ্ধার করে না — এটি উপরের অফিসিয়াল Unsplash API-এর মতো একই বিভাগে পড়ে; আর এটি ফ্রি-লাইসেন্স ফটোগ্রাফি বহন করে, সম্পাদকীয় বা ব্র্যান্ড ইমেজারি নয়।
যেই অংশটি সত্যিকার অর্থে নতুন সেটি উপরের অ্যাসিস্ট্যান্টগুলোকে লক্ষ্য করে তৈরি: mcp.pexafy.com/mcp-এ একটি MCP সার্ভার মানে হলো এমন একটি মডেল যা অন্যথায় স্মৃতি থেকে একটি ইমেজ URL আউড়ে দিত, তা এখন একটি আসল ক্যাটালগ সার্চ করতে পারে এবং এমন একটি ফটো রিটার্ন করতে পারে যা বাস্তবে বিদ্যমান, তার ক্রেডিট লাইনসহ। মেশিন-লিখিত মৃত URL-এর এটি যেকোনো lint নিয়মের চেয়ে ভালো উত্তর, আর যুক্তিটি লেখা আছে
AI এজেন্টদের জন্য ইমেজ সার্চ ইনফ্রাস্ট্রাকচার-এ।
দশ মিনিটে আপনার সম্পদ অডিট করুন
আপনি যেখানেই মাইগ্রেট করুন না কেন, প্রথমে এই অংশটি করুন — আপনি যে URLগুলো খুঁজে পাননি সেগুলো ঠিক করতে পারবেন না। Source আজকের একটি উদাহরণ মাত্র; আপনার এমবেড করা প্রতিটি বহিরাগত অ্যাসেটের জন্য একই তিনটি ধাপ প্রযোজ্য।
# ১. রিপোর ভেতরের সবকিছু, ডকুমেন্টেশন, টেস্ট, ফিক্সচার এবং README সহ।
grep -rn --binary-files=without-match \
-e "source.unsplash.com" -e "via.placeholder.com" -e "/placeholder.png" .
# ২. ডেটাবেস যা ধারণ করে তার সবকিছু — CMS বডিতেই এগুলো সবচেয়ে বেশি দিন লুকিয়ে থাকে।
psql -c "SELECT id FROM posts WHERE body LIKE '%source.unsplash.com%'"
# ৩. বিল্ড করা সাইট আসলে যা রিকোয়েস্ট করে তার সবকিছু: ক্রল করুন এবং ব্যর্থতাগুলো তালিকাভুক্ত করুন।
# অ্যাট্রিবিউট মিলান, ফাইল এক্সটেনশন নয় — ইমেজ URL খুব কমই .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 ← আপনি যা খুঁজছেন
তারপর একবার সিদ্ধান্ত নিন, বহিরাগত ইমেজগুলোকে আপনার কতটা খরচ করার অনুমতি দেওয়া হবে। চারটি নিয়ম যা পরবর্তী শাটডাউনের পরেও টিকে থাকবে, যে-ই এর কারণ হোক না কেন:
- বিল্ড টাইমে ফেচ করুন, রিকোয়েস্ট টাইমে নয়। বিল্ডের সময় resolve করা একটি ইমেজ CI-তে ব্যর্থ হয়, একজন ডেভেলপারের সামনে, ব্যবহারকারীর সামনে রাত ৩টায় নয়।
- কখনোই একটি সাজসজ্জামূলক অ্যাসেটকে একটি ক্রিটিক্যাল পথ ব্লক করতে দেবেন না। একটি লগইন ফর্মের উপরে বহিরাগত কিছুই preload করবেন না; প্রতিটি থার্ড-পার্টি
<img>-কে একটিonerrorfallback এবং স্পষ্টwidth/heightদিন যাতে একটি ব্যর্থতার মূল্য হয় একটি ফাঁকা বাক্স, কোনো লেআউট শিফট বা থমকে যাওয়া স্ক্রিপ্ট নয়। - চেকটি CI-তে যোগ করুন। উপরের ধাপ ৩, আপনার বিল্ড করা আউটপুটের উপর চালান, “কেউ শেষমেশ লক্ষ্য করেছে”-কে একটি লাল বিল্ডে রূপান্তরিত করে। পুনরাবৃত্তি প্রতিরোধ করার এটাই একমাত্র ধাপ।
- আপনার বহিরাগত নির্ভরতাগুলোর জন্য অন্য যেকোনো কিছুর মতোই বাজেট করুন। লিখে রাখুন আপনার পেজগুলো কোন হোস্টের উপর নির্ভর করার অনুমতিপ্রাপ্ত এবং প্রতিটি বন্ধ হলে কী ঘটে। একটি URL যার জন্য আপনাকে রেজিস্টার করতে হয়নি সেটিও একটি নির্ভরতা — Source প্রমাণ করেছে এটি এমন একটি নির্ভরতা যার মালিক আসলে কেউ নয়।
সূত্র ও পাদটীকা
1 এই আর্টিকেলের প্রতিটি স্ট্যাটাস কোড, গণনা এবং উদ্ধৃত লাইন
30 আগস্ট 2026 তারিখে তার প্রাথমিক সূত্র থেকে নেওয়া হয়েছিল। HTTP স্ট্যাটাস কোডগুলো প্রতিটি URL প্যাটার্নের বিরুদ্ধে curl দিয়ে নেওয়া হয়েছিল; সবগুলোই
server: Heroku এবং একটি বডি যা herokucdn.com/error-pages/application-error.html এম্বেড করে সহ 503 রিটার্ন করেছিল। DNS resolution একই দিনে নিশ্চিত করা হয়েছিল (একটি herokudns.com হোস্টের CNAME)।
2 উভয় changelog এন্ট্রি
unsplash.com/documentation/changelog থেকে হুবহু উদ্ধৃত। deprecation-policy শব্দচয়ন (৩ সপ্তাহের নোটিস, Warning হেডার, এবং non-publicly documented এন্ডপয়েন্টের ছাড়) একই দিনে unsplash.com/documentation থেকে নেওয়া।
3 TLS ব্যর্থতা
openssl s_client -connect changelog.unsplash.com:443 দিয়ে reproduce করা হয়েছে (tlsv1 alert internal
error)। রিডাইরেক্ট চেইন curl -L দিয়ে ফলো করা হয়েছে। আর্কাইভের ফাঁকটি Wayback CDX API থেকে নেওয়া: changelog.unsplash.com-এর শেষ 200 ক্যাপচার 20240324-এ, unsplash.com/documentation/changelog-এর প্রথমটি 20240823-এ।
4 ইস্যুর শিরোনাম, তৈরি এবং বন্ধ হওয়ার তারিখ GitHub REST
API থেকে পড়া হয়েছে (mui/material-ui#42736, nextcloud/unsplash#115,
sindresorhus/Actions#248) এবং gin_login ইস্যু 3324054-এর জন্য drupal.org JSON API থেকে, যার বডিতে নভেম্বর 2022-এ “I get always a Heroku application
error” রিপোর্ট করা হয়েছে।
5 গণনা: GitHub কোড সার্চ API
(3,344টি ফাইল; একটি 100-ফলাফলের স্যাম্পেল 77টি রিপোজিটরিতে ডিডুপ্লিকেট করা, যার মধ্যে 12টি 11 জুন 2024-এর পরে তৈরি হয়েছিল এবং 20টি পূর্ববর্তী 12 মাসের মধ্যে পুশ করা হয়েছিল); npm রেজিস্ট্রি ডাউনলোড API
(unsplash-source-es6, পূর্ববর্তী 30 দিনে 23টি ডাউনলোড); Stack Exchange /search/excerpts (1,459টি পোস্ট)। কোড সার্চ শুধু ইনডেক্স করা পাবলিক রিপোজিটরি কভার করে, তাই প্রতিটি সংখ্যাই একটি সর্বনিম্ন সীমা।
প্রাথমিক সূত্র: 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।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
source.unsplash.com কি ডাউন, নাকি স্থায়ীভাবে বন্ধ হয়ে গেছে?
/random, /1600x900/?query, /collection/…, /daily) এখন Heroku-র সাধারণ Application Error পেজসহ HTTP 503 রিটার্ন করে। হোস্টনেমটি এখনও রিজলভ হয়, তাই ব্যর্থতা নেটওয়ার্ক এরর হিসেবে না দেখিয়ে ভাঙা ইমেজ হিসেবে দেখা যায়।source.unsplash.com/random-এর সরাসরি বিকল্প কী?
images.unsplash.com/photo-…?w=1600&h=900&fit=crop — কোনো key লাগে না কিন্তু সবসময় একই ছবি রিটার্ন করে, যা বেশিরভাগ ডেকোরেটিভ ব্যবহারের জন্য আসলে যথেষ্ট। অফিসিয়াল API, GET https://api.unsplash.com/photos/random একটি Authorization: Client-ID হেডারসহ, র্যান্ডমনেস ফিরিয়ে দেয় কিন্তু অবশ্যই সার্ভার-সাইড থেকে কল করতে হবে। আপনার নিজের একটি ছোট প্রক্সি এই এন্ডপয়েন্টের সামনে বসিয়ে একমাত্র এমন key-বিহীন URL ফিরিয়ে দেওয়া যায় যা সরাসরি একটি <img> ট্যাগে বসানো যায়।২০২৬ সালেও কেন AI কোডিং টুলগুলো source.unsplash.com URL জেনারেট করছে?
API key ছাড়াই কি এখনও র্যান্ডম Unsplash ছবি পাওয়া যায়?
/photos/random-এর পেছনে থাকে, যার জন্য একটি Client-ID প্রয়োজন। আপনার দুটি key-বিহীন রুট হলো: নিজে হোস্ট করা একটি প্রক্সি, যেখানে key সার্ভার-সাইডেই থাকে এবং পাবলিক URL পুরনোটার মতোই দেখায়, অথবা Lorem Picsum-এর (picsum.photos/800/600) মতো একটি থার্ড-পার্টি প্লেসহোল্ডার সার্ভিস, যা key ছাড়াই আসল ছবি সরবরাহ করে কিন্তু বিষয়বস্তু টার্গেটিং একেবারেই নেই।Unsplash API কি ছবি ডাউনলোড ও নিজস্ব সার্ভারে হোস্ট করার অনুমতি দেয়?
ixid প্যারামিটার রেখে দিন, ফটোগ্রাফার ও Unsplash-কে ক্রেডিট দিন, এবং ব্যবহারকারী ফাইলটি নিলে ছবির ডাউনলোড এন্ডপয়েন্ট ফায়ার করুন। ফাইলগুলো আপনার নিজস্ব CDN-এ মিরর করাই একমাত্র অপ্টিমাইজেশন যা আপনি নিতে পারবেন না।২০২১-এর ডিপ্রিকেশন নোটিশ কেন বিদ্যমান ব্যবহারকারীদের রক্ষা করেনি?
Warning হেডার — শুধুমাত্র পাবলিকলি ডকুমেন্টেড ফিল্ড ও এন্ডপয়েন্টের ক্ষেত্রে প্রযোজ্য, এবং একই প্যারাগ্রাফে বলা আছে যে ডকুমেন্ট না করা যেকোনো কিছু কোনো সতর্কতা ছাড়াই পরিবর্তিত হতে পারে। Source কখনোই API-এর একটি ডকুমেন্টেড এন্ডপয়েন্ট ছিল না, তাই এটি সেই পলিসির বাইরে ছিল যা একে রক্ষা করতে পারত।আমার প্রজেক্টে থাকা প্রতিটি মৃত source.unsplash.com URL কীভাবে খুঁজে বের করব?
grep -rn "source.unsplash.com" . — কারণ এই URL গুলো স্যাম্পল কোডে সবচেয়ে বেশি সময় টিকে থাকে। ডেটাবেস কুয়েরি করুন, কারণ CMS আর্টিকেল বডিতেই এগুলো লুকিয়ে থাকে (WHERE body LIKE '%source.unsplash.com%')। তারপর আপনার বিল্ট আউটপুট ক্রল করুন: প্রতিটি ইমেজ URL এক্সট্র্যাক্ট করে প্রতিটির রিকোয়েস্ট পাঠান, 200 নয় এমন যেকোনো কিছু তালিকাভুক্ত করুন। এই শেষ ধাপটি CI-তে যোগ করুন যাতে একটি মৃত থার্ড-পার্টি অ্যাসেট পেজের বদলে বিল্ড ফেইল করায়।