{"id":39775,"date":"2026-02-04T07:00:00","date_gmt":"2026-02-04T15:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=39775"},"modified":"2026-02-04T11:00:11","modified_gmt":"2026-02-04T19:00:11","slug":"auto-inclusive-web-tanstack-start-and-cloudinary-ai","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai","title":{"rendered":"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p><a href=\"https:\/\/github.com\/musebe\/auto-inclusive-web\"><strong>GitHub Repository<\/strong><\/a> | <a href=\"https:\/\/auto-inclusive-web.vercel.app\/gallery\"><strong>Live Demo<\/strong><\/a><\/p>\n<p>Imagine a web where every image is instantly understandable to everyone, including the 2.2 billion people globally with vision impairments. This is a massive engineering challenge. Developers often view writing descriptive alt text as a manual chore that slows down release cycles.<\/p>\n<p>This platform solves that crisis by integrating accessibility into your workflow, rather than a manual, extra step. Powered by a high-performance TanStack Start foundation and Cloudinary AI v5, it generates, saves, and streams accessible metadata for every asset the moment it hits the pipeline.<\/p>\n<p>In this tutorial, you\u2019ll architect a self-healing accessibility system that:<\/p>\n<ol>\n<li>Bootstraps a type-safe foundation using TanStack Start\u2019s full-stack framework.<\/li>\n<li>Ingests assets via a professional Pipeline Entry Point using the Cloudinary Upload Widget.<\/li>\n<li>Uses server-side scripts to commit AI captions to permanent storage.<\/li>\n<li>Orchestrates metadata via type-safe server functions to deliver a WCAG-compliant gallery.<\/li>\n<\/ol>\n<h2>Bootstrapping the Full-Stack Foundation<\/h2>\n<p>To build an autonomous pipeline, we\u2019ll first need a type-safe, full-stack environment.<\/p>\n<p>We\u2019ll use <a href=\"https:\/\/tanstack.com\/start\/latest\"><strong>TanStack Start<\/strong><\/a>, a framework that allows us to move heavy metadata orchestration to the server while keeping the UI responsive and inclusive.<\/p>\n<h3>Initializing TanStack Start<\/h3>\n<p>We\u2019ll start by scaffolding the project using the official CLI.<\/p>\n<p>This gives us a structured environment with built-in support for server functions and advanced routing.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"># <span class=\"hljs-selector-tag\">Create<\/span> <span class=\"hljs-selector-tag\">a<\/span> <span class=\"hljs-selector-tag\">new<\/span> <span class=\"hljs-selector-tag\">project<\/span> <span class=\"hljs-selector-tag\">with<\/span> <span class=\"hljs-selector-tag\">React<\/span> <span class=\"hljs-selector-tag\">and<\/span> <span class=\"hljs-selector-tag\">TypeScript<\/span>\n<span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">create<\/span> <span class=\"hljs-keyword\">@tanstack<\/span>\/start<span class=\"hljs-keyword\">@latest<\/span> auto-inclusive-web\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>After choosing the default options, install the core dependencies for Cloudinary integration.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install cloudinary\n<\/code><\/span><\/pre>\n<h3>The Document Shell: Global SDK Injection<\/h3>\n<p>Because we\u2019ll build a professional ingestion point, we\u2019ll need the Cloudinary Upload Widget available globally.<\/p>\n<p>We\u2019ll inject the script directly into the <code>head<\/code> of our <code>src\/routes\/__root.tsx<\/code> file.<\/p>\n<p>This ensures that every page in our app has the power to deploy inclusive assets.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/routes\/__root.tsx (snippet)<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createRootRoute({\n  <span class=\"hljs-attr\">head<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> ({\n    <span class=\"hljs-attr\">scripts<\/span>: &#91;\n      {\n        <span class=\"hljs-attr\">src<\/span>: <span class=\"hljs-string\">'https:\/\/upload-widget.cloudinary.com\/latest\/global\/all.js'<\/span>,\n        <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">'text\/javascript'<\/span>,\n      },\n    ],\n  }),\n  <span class=\"hljs-attr\">shellComponent<\/span>: RootDocument,\n});\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Environment Topology<\/h3>\n<p>Managing secrets is a critical \u201cgotcha\u201d in full-stack apps.<\/p>\n<p>We\u2019ll split our environment variables into two categories.<\/p>\n<ul>\n<li>\n<strong><code>VITE_<\/code> prefixed.<\/strong> Public keys (like your cloud name and upload preset) that the browser needs to initialize the Upload Widget.<\/li>\n<li>\n<strong>Server-only.<\/strong> Private keys (like your <code>CLOUDINARY_API_SECRET<\/code>) that remain on the server to handle secure Admin API calls.<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># .env (local configuration)<\/span>\nVITE_CLOUDINARY_CLOUD_NAME=<span class=\"hljs-string\">\"your_name\"<\/span>\nVITE_CLOUDINARY_UPLOAD_PRESET=<span class=\"hljs-string\">\"auto_inclusive_preset\"<\/span>\n\n<span class=\"hljs-comment\"># PRIVATE: Only accessible in TanStack Server Functions<\/span>\nCLOUDINARY_API_KEY=<span class=\"hljs-string\">\"your_key\"<\/span>\nCLOUDINARY_API_SECRET=<span class=\"hljs-string\">\"your_secret\"<\/span>\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Activating the Intelligence Layer<\/h3>\n<p>The \u201csmarts\u201d of our pipeline come from the <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_add_ons\"><strong>Cloudinary Add-ons<\/strong><\/a> marketplace.<\/p>\n<p>Before moving to the code, you must activate the following in your Cloudinary Console:<\/p>\n<ol>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_ai_content_analysis_addon\"><strong>AI Content Analysis<\/strong><\/a>. This is the v5 engine that writes our natural language descriptions.<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/translation_addons\"><strong>Google Translation<\/strong><\/a>. Optional but recommended for localizing your inclusive metadata for global audiences.<\/li>\n<\/ol>\n<h2>Cloudinary Environment Setup<\/h2>\n<p>To give our pipeline \u201csight,\u201d we\u2019ll configure the Cloudinary ecosystem. This is where we activate the AI models and create the rules that govern our autonomous metadata generation.<\/p>\n<h3>Activating the AI Marketplace<\/h3>\n<p>Before writing code, you\u2019ll need to \u201chire\u201d the AI agents that will process your assets. Navigate to the <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_add_ons\"><strong>Cloudinary Add-ons<\/strong><\/a> page in your console and register for:<\/p>\n<ol>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_ai_content_analysis_addon\"><strong>Cloudinary AI Content Analysis<\/strong><\/a>. The powerhouse v5 engine. It doesn\u2019t just tag \u201cdog\u201d or \u201ctree\u201d; it generates full natural-language captions (e.g., <em>\u201cA golden retriever puppy playing with a red ball in the grass\u201d<\/em>).<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/translation_addons\"><strong>Google Translation<\/strong><\/a>. This add-on allows us to take that AI caption and instantly localize it, ensuring our inclusive metadata reaches a global audience.<\/li>\n<\/ol>\n<h3>The Secure Upload Preset<\/h3>\n<p>We don\u2019t want to pass complex AI instructions from the client-side for every upload. Instead, we\u2019ll create an <strong>Upload Preset<\/strong> (let\u2019s call it <code>auto_inclusive_preset<\/code>). This acts as a predefined instruction manual for Cloudinary.<\/p>\n<ol>\n<li>\n<strong>Settings<\/strong> &gt; <strong>Upload<\/strong> &gt; <strong>Add Upload Preset<\/strong>.<\/li>\n<li>\n<strong>Signing mode.<\/strong> Set to <strong>Unsigned<\/strong>. This allows our TanStack Start frontend to upload directly to Cloudinary without a secure server-side signature for every file.<\/li>\n<li>\n<strong>Folder.<\/strong> Set to <code>inclusive_web_assets<\/code> to keep our library organized.<\/li>\n<\/ol>\n<h3>Configuring AI Detection<\/h3>\n<p>Inside the preset, navigate to the <strong>Upload Manipulations<\/strong> or <strong>Add-ons<\/strong> section (depending on your console version). This is the \u201cAha!\u201d configuration:<\/p>\n<ul>\n<li>\n<strong>AI Content Analysis.<\/strong> Enable \u201cAdd AI captioning to your image\u201d.<\/li>\n<li>\n<strong>Google Auto Tagging.<\/strong> Set a confidence threshold (e.g., <code>0.7<\/code>) to categorize your assets automatically for SEO. <em>(Optional)<\/em>\n<\/li>\n<\/ul>\n<h3>The \u2018Gotcha\u2019: On-Success Logic<\/h3>\n<p>One common hurdle identified is that AI data in unsigned uploads can be ephemeral. To ensure the AI caption is permanently saved to the asset\u2019s metadata, we\u2019ll use an <strong>On-success script<\/strong>.<\/p>\n<p>In the <strong>Advanced<\/strong> tab of your preset, add this snippet:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ This runs server-side on Cloudinary immediately after a successful upload<\/span>\n<span class=\"hljs-comment\">\/\/ It commits the v5 AI caption to the asset's permanent 'context' field<\/span>\n\ncurrent_asset.update({\n  <span class=\"hljs-attr\">context<\/span>: { \n    <span class=\"hljs-attr\">caption<\/span>: e.upload_info?.info?.detection?.captioning?.data?.caption \n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>By shifting this logic to the Cloudinary Edge, we\u2019ll ensure that every asset is born with its descriptive metadata committed before our gallery even knows it exists.<\/p>\n<h2>Solving the Persistence Hurdle<\/h2>\n<p>In this section, we\u2019ll address the biggest engineering roadblock when working with browser-based uploads and AI: <strong>The Metadata Disappearing Act<\/strong>.<\/p>\n<h3>The Unsigned Restriction<\/h3>\n<p>When using Unsigned Uploads (which are necessary for simple, client-side widgets), Cloudinary restricts certain parameters for security. Specifically, you can\u2019t pass the <code>detection<\/code> or <code>auto_tagging<\/code> parameters directly from your frontend code.<\/p>\n<p>If you try to trigger AI captioning via a <code>createUploadWidget<\/code> call in your React component, Cloudinary will ignore the request to prevent malicious users from racking up your AI credits.<\/p>\n<h3>Moving Logic to the Edge<\/h3>\n<p>To bypass this, we\u2019ll utilize the same <strong>Upload Preset<\/strong> as our \u201cTrusted Agent.\u201d<\/p>\n<ul>\n<li>\n<strong>The problem.<\/strong> The client can\u2019t ask for AI.<\/li>\n<li>\n<strong>The solution.<\/strong> The client asks to use a preset, and that preset (which lives securely on Cloudinary\u2019s servers) is the one that demands the AI analysis.<\/li>\n<\/ul>\n<p>This architectural shift ensures that the AI analysis is triggered by your server-side configuration, making it secure and reliable.<\/p>\n<h3>Why <code>on_success<\/code> is Mandatory<\/h3>\n<p>Even when the AI runs, the resulting caption is often just a \u201ctransient\u201d piece of data in the upload response. If you don\u2019t explicitly tell Cloudinary to save it, then that caption won\u2019t be indexed in your Media Library for future retrieval by your gallery.<\/p>\n<p>By using the <strong>On-success script<\/strong> we added to the preset, we\u2019ll perform a \u201cMetadata Commit.\u201d<\/p>\n<h3>Verification: Confirming the Commit<\/h3>\n<p>Before writing the gallery code, verify your pipeline is working:<\/p>\n<ol>\n<li>Upload an image using your dashboard.<\/li>\n<li>Open your <a href=\"https:\/\/cloudinary.com\/console\/media_library\">Cloudinary Media Library<\/a>.<\/li>\n<li>Click the image and open the <strong>Context<\/strong> or <strong>Metadata<\/strong> tab.<\/li>\n<li>You should see a key named <code>caption<\/code> populated with a full sentence generated by the AI.<\/li>\n<\/ol>\n<p>If the caption is there, your \u201cZero-Touch\u201d pipeline is officially live.<\/p>\n<h2>Building the Metadata Orchestrator<\/h2>\n<p>With our AI-enriched assets stored safely in Cloudinary, we\u2019ll now need a bridge to bring that data into our frontend.<\/p>\n<p>In <strong>TanStack Start<\/strong>, we\u2019ll use <strong>Server Functions<\/strong> (<code>createServerFn<\/code>) to securely fetch our assets without exposing our <code>API_SECRET<\/code> to the client.<\/p>\n<h3>Why a Server Function?<\/h3>\n<p>The Cloudinary Admin API is the only way to retrieve the <code>context<\/code> metadata we saved in the previous section, and requires your private Secret Key.<\/p>\n<p>If you called this from a standard React component, your keys would be visible in the <strong>Network<\/strong> tab of the browser, a massive security risk.<\/p>\n<p>By using <code>createServerFn<\/code>, TanStack Start ensures the code runs only on the server, acting as a secure proxy.<\/p>\n<h3>The Fetch Engine<\/h3>\n<p>This is the \u201cbrain\u201d of your data layer. We\u2019ll keep the implementation lean by focusing on the parameters that matter most. Notice how we explicitly request <code>context: true<\/code>? Without this, Cloudinary will omit the AI-generated captions.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/utils\/gallery-engine.ts (Simplified)<\/span>\n<span class=\"hljs-keyword\">import<\/span> { createServerFn } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@tanstack\/start\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ Configure the SDK (Server-Side Only)<\/span>\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.VITE_CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> fetchGallery = createServerFn({ <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"GET\"<\/span> }).handler(\n  <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> cloudinary.api.resources({\n      <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">\"upload\"<\/span>,\n      <span class=\"hljs-attr\">prefix<\/span>: <span class=\"hljs-string\">\"inclusive_web_assets\/\"<\/span>,\n      <span class=\"hljs-attr\">context<\/span>: <span class=\"hljs-literal\">true<\/span>, <span class=\"hljs-comment\">\/\/ MANDATORY: This fetches our AI captions<\/span>\n      <span class=\"hljs-attr\">max_results<\/span>: <span class=\"hljs-number\">50<\/span>,\n    });\n\n    <span class=\"hljs-comment\">\/\/ Transform raw Cloudinary data into clean UI props<\/span>\n    <span class=\"hljs-keyword\">return<\/span> result.resources.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">asset: any<\/span>) =&gt;<\/span> ({\n      <span class=\"hljs-attr\">publicId<\/span>: asset.public_id,\n      <span class=\"hljs-attr\">url<\/span>: asset.secure_url,\n      <span class=\"hljs-comment\">\/\/ Map the buried AI caption to a clean 'alt' field<\/span>\n      <span class=\"hljs-attr\">alt<\/span>: asset.context?.custom?.caption || <span class=\"hljs-string\">\"AI Description processing...\"<\/span>,\n    }));\n  }\n);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>View the full implementation on GitHub: <a href=\"https:\/\/github.com\/musebe\/auto-inclusive-web\/blob\/main\/src\/utils\/gallery-engine.ts\"><code>src\/utils\/gallery-engine.ts<\/code><\/a><\/p>\n<\/blockquote>\n<h3>The \u2018Gotcha\u2019: Payload Mapping<\/h3>\n<p>When Cloudinary returns your assets, the AI caption isn\u2019t in a top-level <code>alt<\/code> field. It\u2019s buried inside <code>context.custom.caption<\/code>.<\/p>\n<ul>\n<li>\n<strong>The problem.<\/strong> Your UI shouldn\u2019t have to know about Cloudinary\u2019s deep JSON structure.<\/li>\n<li>\n<strong>The solution.<\/strong> We\u2019ll \u201cmap\u201d the data inside the server function.<\/li>\n<\/ul>\n<p>This keeps our frontend components clean and focused only on rendering.<\/p>\n<h3>Consuming the Data in a Route<\/h3>\n<p>Now, you can use this function in your Gallery route.<\/p>\n<p>TanStack Start makes this feel like a standard React hook, but it\u2019s actually performing a type-safe network call to your server.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/routes\/gallery.tsx (snippet)<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createFileRoute(<span class=\"hljs-string\">'\/gallery'<\/span>)({\n  <span class=\"hljs-attr\">loader<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> fetchGallery(),\n  <span class=\"hljs-attr\">component<\/span>: GalleryComponent,\n});\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GalleryComponent<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> assets = Route.useLoaderData(); <span class=\"hljs-comment\">\/\/ Fully typed array of our mapped assets!<\/span>\n  <span class=\"hljs-comment\">\/\/ ... render the inclusive gallery<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>The Pipeline Entry Point<\/h2>\n<p>Building an autonomous pipeline requires a UI that signals its purpose. We don\u2019t just want a \u201cfile input\u201d; we want a professional <strong>Pipeline Entry Point<\/strong> that indicates assets are being ingested into an AI workflow.<\/p>\n<h3>Affordance Design: The \u201cDashed-Zone\u201d<\/h3>\n<p>To make the intent unmistakable, we use the universal UI language for uploads. A large, dashed-border container with high-contrast icons. This creates an immediate mental model for the user and is where files go to be processed.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/components\/UploadWidget.tsx (Simplified snippet)<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">UploadWidget<\/span>(<span class=\"hljs-params\">{ onUploadSuccess }: UploadWidgetProps<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;isOpening, setIsOpening] = useState(<span class=\"hljs-literal\">false<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> openWidget = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    setIsOpening(<span class=\"hljs-literal\">true<\/span>);\n    <span class=\"hljs-comment\">\/\/ @ts-ignore - Cloudinary is attached to window via root script<\/span>\n    <span class=\"hljs-keyword\">const<\/span> widget = <span class=\"hljs-built_in\">window<\/span>.cloudinary.createUploadWidget(\n      {\n        <span class=\"hljs-attr\">cloudName<\/span>: <span class=\"hljs-keyword\">import<\/span>.meta.env.VITE_CLOUDINARY_CLOUD_NAME,\n        <span class=\"hljs-attr\">uploadPreset<\/span>: <span class=\"hljs-keyword\">import<\/span>.meta.env.VITE_CLOUDINARY_UPLOAD_PRESET, <span class=\"hljs-comment\">\/\/ Uses Section 3 settings<\/span>\n        <span class=\"hljs-attr\">sources<\/span>: &#91;<span class=\"hljs-string\">\"local\"<\/span>, <span class=\"hljs-string\">\"url\"<\/span>, <span class=\"hljs-string\">\"camera\"<\/span>],\n      },\n      (error, result) =&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (!error &amp;&amp; result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n          onUploadSuccess(result.info);\n        }\n        setIsOpening(<span class=\"hljs-literal\">false<\/span>);\n      }\n    );\n    widget.open();\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n      <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{openWidget}<\/span>\n      <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full border-2 border-dashed border-slate-200 rounded-&#91;2.5rem] p-12 hover:border-indigo-400 hover:bg-slate-50 transition-all\"<\/span>\n    &gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UploadIcon<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-10 h-10 mb-8\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-2xl font-black text-slate-900 uppercase\"<\/span>&gt;<\/span>\n        {isOpening ? \"Connecting...\" : \"Deploy Inclusive Asset\"}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-indigo-600 font-bold text-xs uppercase tracking-widest mt-2\"<\/span>&gt;<\/span>\n        Click to browse or drag and drop\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>View the full implementation on GitHub: <a href=\"https:\/\/github.com\/musebe\/auto-inclusive-web\/blob\/main\/src\/components\/UploadWidget.tsx\"><code>src\/components\/UploadWidget.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Managing the Ingestion State<\/h3>\n<p>One major \u201cUX Gotcha\u201d is the delay between clicking a button and the widget appearing.<\/p>\n<p>By using an <code>isOpening<\/code> state, we:<\/p>\n<ul>\n<li>Disable the button to prevent double-initialization.<\/li>\n<li>Change the text to \u201cInitializing AI Pipeline\u201d or \u201cConnecting\u2026\u201d.<\/li>\n<li>Provide visual feedback that the \u201chandshake\u201d between your app and Cloudinary is happening.<\/li>\n<\/ul>\n<h2>The Inclusive Asset Library<\/h2>\n<p>Rendering an inclusive library displays images and presents AI-generated metadata as an integral part of the UI. This is where we turn raw data into a human-centric experience.<\/p>\n<h3>Mapping Metadata to the DOM<\/h3>\n<p>Once our server function delivers the mapped assets, we\u2019ll render them using a semantic and accessible grid. The key is ensuring the <code>alt<\/code> text is correctly applied to the <code>&lt;img&gt;<\/code> tag.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ app\/routes\/gallery.tsx (Simplified snippet)<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GalleryComponent<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> assets = Route.useLoaderData();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-1 gap-12\"<\/span>&gt;<\/span>\n      {assets.map((asset) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n          <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{asset.publicId}<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col md:flex-row gap-8 items-center bg-white rounded-&#91;2rem] p-8 border border-slate-50 shadow-xl\"<\/span>\n        &gt;<\/span>\n          {\/* 1. Optimized Visual Asset *\/}\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full md:w-&#91;45%] aspect-&#91;4\/3]\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n              <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{asset.url.replace(<\/span>\n                \"\/<span class=\"hljs-attr\">upload<\/span>\/\",\n                \"\/<span class=\"hljs-attr\">upload<\/span>\/<span class=\"hljs-attr\">f_auto<\/span>,<span class=\"hljs-attr\">q_auto<\/span>,<span class=\"hljs-attr\">c_pad<\/span>,<span class=\"hljs-attr\">ar_4:3<\/span>,<span class=\"hljs-attr\">b_white<\/span>\/\"\n              )}\n              <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{asset.alt}<\/span> \/\/ <span class=\"hljs-attr\">The<\/span> <span class=\"hljs-attr\">AI-generated<\/span> <span class=\"hljs-attr\">mission-critical<\/span> <span class=\"hljs-attr\">metadata<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full h-full object-contain rounded-2xl\"<\/span>\n            \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n          {\/* 2. Metadata Context *\/}\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full md:w-&#91;55%] space-y-4\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-&#91;10px] font-black text-indigo-600 uppercase tracking-widest\"<\/span>&gt;<\/span>\n              Autonomous Caption\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-xl font-serif italic text-slate-700 leading-relaxed\"<\/span>&gt;<\/span>\n              \"{asset.alt}\"\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex gap-2\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">TechBadge<\/span> <span class=\"hljs-attr\">label<\/span>=<span class=\"hljs-string\">\"WCAG 2.1 AA\"<\/span> \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">TechBadge<\/span> <span class=\"hljs-attr\">label<\/span>=<span class=\"hljs-string\">\"Cloudinary AI v5\"<\/span> \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      ))}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>View the full implementation on GitHub: <a href=\"https:\/\/github.com\/musebe\/auto-inclusive-web\/blob\/main\/src\/routes\/gallery.tsx\"><code>src\/routes\/gallery.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h2>Conclusion and Future-Proofing<\/h2>\n<p>By architecting this \u201cZero-Touch\u201d pipeline, we\u2019ve moved accessibility from an item on a checklist to an immutable part of the asset lifecycle.<\/p>\n<p>The current pipeline sets the stage for even deeper global inclusivity. By layering the <a href=\"https:\/\/cloudinary.com\/documentation\/translation_addons\"><strong>Google Translation Add-on<\/strong><\/a> into our <code>on_success<\/code> script, we can instantly localize these AI captions into dozens of languages.<\/p>\n<p>An image uploaded in Kenya is instantly accessible to a screen-reader user in Tokyo, localized in Japanese, without a single human intervention. That\u2019s the power of an auto-inclusive web. Ready to try this build for yourself? <a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up<\/a> for a free Cloudinary account today.<\/p>\n<ul>\n<li>Live implementation: <a href=\"https:\/\/auto-inclusive-web.vercel.app\/gallery\">auto-inclusive-web.vercel.app\/gallery<\/a>\n<\/li>\n<li>Full source code: <a href=\"https:\/\/github.com\/musebe\/auto-inclusive-web\">github.com\/musebe\/auto-inclusive-web<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":39776,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[337,336],"class_list":["post-39775","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-accessibility","tag-ai"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-04T15:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-04T19:00:11+00:00\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI\",\"datePublished\":\"2026-02-04T15:00:00+00:00\",\"dateModified\":\"2026-02-04T19:00:11+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai\"},\"wordCount\":10,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA\",\"keywords\":[\"Accessibility\",\"AI\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2026\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai\",\"url\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai\",\"name\":\"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA\",\"datePublished\":\"2026-02-04T15:00:00+00:00\",\"dateModified\":\"2026-02-04T19:00:11+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA\",\"width\":4000,\"height\":2200},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai","og_locale":"en_US","og_type":"article","og_title":"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI","og_url":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai","og_site_name":"Cloudinary Blog","article_published_time":"2026-02-04T15:00:00+00:00","article_modified_time":"2026-02-04T19:00:11+00:00","author":"melindapham","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI","datePublished":"2026-02-04T15:00:00+00:00","dateModified":"2026-02-04T19:00:11+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai"},"wordCount":10,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA","keywords":["Accessibility","AI"],"inLanguage":"en-US","copyrightYear":"2026","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai","url":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai","name":"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA","datePublished":"2026-02-04T15:00:00+00:00","dateModified":"2026-02-04T19:00:11+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA","width":4000,"height":2200},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/auto-inclusive-web-tanstack-start-and-cloudinary-ai#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Architecting an Auto-Inclusive Web With TanStack Start and Cloudinary AI"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1770072854\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI\/Architecting_an_Auto-Inclusive_Web__Solving_Accessibility_Debt_with_TanStack_Start_and_Cloudinary_AI.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39775","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=39775"}],"version-history":[{"count":3,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39775\/revisions"}],"predecessor-version":[{"id":39779,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39775\/revisions\/39779"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/39776"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=39775"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=39775"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=39775"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}