This is a submission for the Netlify Dynamic Site Challenge: Build with Blobs.
What I Built
"I Disappear" is an art project by Boris Duboullay initiated in 2004, which delves into themes of identity, technology, and the inexorable passage of time. The project starts with a digital photograph of the artist's face, made up of 20,000 pixels. Each day, since September 22nd 2004, one pixel is randomly replaced by a white pixel. This process symbolizes the slow fading of identity and the march toward the end of life. After 54 years, which equates to 20,000 days, the photograph will have completely turned white, representing a lifetime's journey towards invisibility.
When I encountered the Netlify Dynamic Site Challenge, I saw a perfect opportunity to modernize the project using modern web technologies while preserving its original intent. Utilizing Netlify's infrastructure, especially Netlify Blobs for data handling, the project has been re-engineered for long-term sustainability. This enhancement ensures that the digital representation of decay and loss can persist. This modernizing on Netlify not only secures the project's long-term viability but also facilitate its management.
Demo
Experience "I Disappear" live by visiting the website at idisappear.net. This digital canvas displays the gradual transformation of the photograph in real time, allowing visitors to witness the slow process of disappearance that the project encapsulates.
The project is technically structured around a matrix representing the 20,000 pixels of the original photograph, with 1
representing visible pixels and 0
representing pixels that have been turned to white. Each day, this matrix is updated by randomly converting a 1
into a 0
, signifying the removal of a pixel. The server-side processing involves a 100% JavaScript implementation of the HTML Canvas 2D drawing API for Node.js, known as PureImage. This workflow processes the photograph daily: integrating the original photograph with the updated matrix on a Canvas instance, generating a new image, and archiving the previous day's image.
For those interested in the technical underpinnings, the complete source code is available on GitHub.
Platform Primitives
In "I Disappear," Netlify's powerful features have been instrumental in achieving both functionality and performance. I use Netlify Blobs extensively for dual purposes: as a database to store the pixel matrix in JSON format and as a storage medium for the daily processed PNG images of the photograph. Each image file is accompanied by metadata specifying the date of the image, which helps in tracking the transformation over time.
Look how easy it is to store my images using Blobs:
// Save picture to Netlify Blob
async function saveImage(blob) {
const metadata = { date: new Date().toISOString() };
await store.set(
`backup_${width * height - pixelCounter.visiblePixels}`,
blob,
{ metadata: metadata }
);
await store.set("picture", blob);
}
The application relies on a scheduled function, executed daily, to update the matrix and process the new image. This automation ensures that the project remains self-sustaining and consistent in its daily updates.
To deliver the images and associated data to users efficiently, I utilize Netlify Edge Functions. These functions manage two specific routes: one for the daily updated image and another for the counter reflecting the progress of the transformation. Given that updates to these elements occur only once per day, I implemented a robust caching strategy to optimize load times and reduce bandwidth usage. This strategy employs a combination of Cache-Control
, Netlify-CDN-Cache-Control
, and ETag
headers, ensuring that the data remains cached at both the user and CDN levels until the next daily update. This approach not only enhances the user experience by speeding up load times but also leverages Netlify's CDN to reduce server load and resource consumption.
Here is the code of the Edge Function that is responsible for returning the number of visible pixels of the picture:
import crypto from "crypto";
import { getStore } from "@netlify/blobs";
export default async (req) => {
const store = getStore("idisappear");
const counter = await store.get("counter");
const etag = `"${crypto.createHash("md5").update(counter).digest("hex")}"`;
const headers = {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=0, must-revalidate", // Tell browsers to always revalidate
"Netlify-CDN-Cache-Control": "public, max-age=31536000, must-revalidate", // Tell Edge to cache asset for up to a year
ETag: etag,
};
if (
req.headers.get("if-none-match") === `W/${etag}` ||
req.headers.get("if-none-match") === etag
) {
return new Response(null, {
status: 304,
headers: { headers },
});
}
return new Response(counter, {
headers: headers,
});
};
export const config = {
cache: "manual",
path: "/api/counter",
};
For the frontend of "I Disappear," I leverage the automated build & deploy system provided by Netlify, which seamlessly integrates with Vite. This setup ensures that every deployment is optimized for performance, utilizing Viteβs modern build tools to enhance speed and efficiency.
Additionally, the Netlify CLI is an indispensable tool in my development workflow. It allows me to develop and test everything locally, mirroring the production environment. By using Netlify CLI, I ensure that all components of the application interact flawlessly before they go live.
Top comments (3)
Cool that you were able to rearchitect something that's been around for so long using modern tools. Well done!
Cool idea!! All the best for your submission
Not sure if it's one or two prompts...
Let's see ;)