{"id":27955,"date":"2022-08-12T07:28:47","date_gmt":"2022-08-12T07:28:47","guid":{"rendered":"http:\/\/how-to-upload-images-to-cloudinary-with-remix-app"},"modified":"2025-02-22T12:29:32","modified_gmt":"2025-02-22T20:29:32","slug":"how-to-upload-images-to-cloudinary-with-remix-app","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/","title":{"rendered":"How to Upload Images to Cloudinary with Remix App"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p><a href=\"https:\/\/remix.run\/\">Remix<\/a> is the newest trending JavaScript framework in the ecosystem right now. It is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience. Personally, i like the fact that Remix is carrying us back to web development foundations with amazing features such as:<\/p>\n<ul>\n<li>\n<p>Nested Routes: Remix loads data in parallel on the server and sends a fully formed HTML document. This almost eliminates loading states and makes your site super lightning fast. You might not need <strong>Skeleton UI<\/strong> anymore. This is good news to most frontend developers like me.<\/p>\n<\/li>\n<li>\n<p>Simple: If you know HTML, you will enjoy working with Remix. For example, you can implement a form without JavaScript. Remix runs actions server side, revalidates data client side, and even handles race conditions from resubmissions.<\/p>\n<\/li>\n<li>\n<p>Error Handling: Remix makes Error handling easy with Error boundaries. While we are still waiting for React to provide Error boundaries for functional components, Remix handles errors while Server Rendering and while Client Rendering too.<\/p>\n<\/li>\n<li>\n<p>Actions and Loaders: After <strong>Nested Routes,<\/strong> this is the next most amazing feature of Remix. In Remix, you use actions for mutations and loaders for retrieving data. Interesting fact about Loaders is that you can do most of your data transformation and calculations there, like check if a list is empty, limit the number of records, only send specific attributes, so your React component just receives the data and renders it, no logic needed.<\/p>\n<p>You can think of loaders as \u201cGET\u201d request handlers, the code snippet below shows how loaders work:<\/p>\n<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { json } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@remix-run\/node\"<\/span>; <span class=\"hljs-comment\">\/\/ or \"@remix-run\/cloudflare\"<\/span>\n    \n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> loader = <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-comment\">\/\/ The `json` function converts a serializable object into a JSON response<\/span>\n  <span class=\"hljs-comment\">\/\/ All loaders must return a `Response` object.<\/span>\n      <span class=\"hljs-keyword\">return<\/span> json({ <span class=\"hljs-attr\">ok<\/span>: <span class=\"hljs-literal\">true<\/span> });\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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>The <code>loader()<\/code> function is only run on the server. Later in this article, we will be making use of loaders.<\/p>\n<ul>\n<li>Lightning page speed: With nested routes, instant transitions leveraging distributed systems and native browser features, built on web fetch api, and cloudfare workers, Remix could have 99 problems, but page speed ain\u2019t one.<\/li>\n<\/ul>\n<p><a href=\"http:\/\/cloudinary.com\">Cloudinary<\/a> is a platform on which we can upload, store, manage, transform, and deliver images and videos for web and mobile applications. Cloudinary provides an exhaustive API for uploading media (including images, video and audio). The Upload API enables you to upload your media assets (resources) and provides a wide range of functionality, including basic and advanced <a href=\"https:\/\/cloudinary.com\/documentation\/image_upload_api_reference#asset_management\">asset management<\/a>, <a href=\"https:\/\/cloudinary.com\/documentation\/image_upload_api_reference#metadata_management\">metadata management<\/a>, and <a href=\"https:\/\/cloudinary.com\/documentation\/image_upload_api_reference#asset_generation\">asset generation<\/a>.\n<a href=\"https:\/\/chakra-ui.com\/\">Chakra UI<\/a> is a simple, modular and accessible component library that gives you the building blocks you need to build your React applications.<\/p>\n<p>In this tutorial, we will be building a Remix app that allows us to upload images to Cloudinary.<\/p>\n<h2>Pre-requisites<\/h2>\n<p>You will need the following to follow this tutorial:<\/p>\n<ul>\n<li>Nodejs &gt;=v14 installed<\/li>\n<li>Knowledge of JavaScript<\/li>\n<li>A code editor (preferably VSCode)<\/li>\n<\/ul>\n<p>The complete code is on <a href=\"https:\/\/codesandbox.io\/embed\/keen-pike-s7cqnc?fontsize=14&amp;hidenavigation=1&amp;theme=dark\">Codesandbox<\/a>.<\/p>\n<blockquote>\n<p>Disclaimer: Codesanbox does not support Remix out of the box yet. To view this demo on Codesandbox, fork the project, provide your cloudinary credentials, open the terminal, and run these commands<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">cd my-remix-app\nnpm run dev\n<\/code><\/span><\/pre>\n<p>The app will be running on port 3000.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/keen-pike-s7cqnc?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"keen-pike-s7cqnc?\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Getting Started<\/h2>\n<p>Let\u2019s begin setting up our Remix project. Run this command to bootstrap a new Remix project<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-id\">#bash<\/span>\n<span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">create-remix<\/span><span class=\"hljs-keyword\">@latest<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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>When the script is done installing, you\u2019ll be prompted with some questions, make sure you choose these options:<\/p>\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\">#bash<\/span>\n? Where would you like to create your app? remix-upload-image\n? What type of app <span class=\"hljs-keyword\">do<\/span> you want to create? Just the basics\n? Where <span class=\"hljs-keyword\">do<\/span> you want to deploy? Choose Remix App Server <span class=\"hljs-keyword\">if<\/span> you<span class=\"hljs-string\">'re unsure; it'<\/span>s easy to change deployment targets. Remix App Server\n? TypeScript <span class=\"hljs-keyword\">or<\/span> JavaScript? JavaScript\n? <span class=\"hljs-keyword\">Do<\/span> you want me to run `npm install`? Yes\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<p>Once it\u2019s done installing dependencies, run this command to change directory to our project directory:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-id\">#bash<\/span>\n<span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">remix-upload-image<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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>Open the folder with your code editor, and let\u2019s go through the directory structure of our remix app.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-id\">#bash<\/span>\n.\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">README<\/span><span class=\"hljs-selector-class\">.md<\/span>\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">app<\/span>\n\u2502   \u251c\u2500\u2500 <span class=\"hljs-selector-tag\">entry<\/span><span class=\"hljs-selector-class\">.client<\/span><span class=\"hljs-selector-class\">.jsx<\/span>\n\u2502   \u251c\u2500\u2500 <span class=\"hljs-selector-tag\">entry<\/span><span class=\"hljs-selector-class\">.server<\/span><span class=\"hljs-selector-class\">.jsx<\/span>\n\u2502   \u251c\u2500\u2500 <span class=\"hljs-selector-tag\">root<\/span><span class=\"hljs-selector-class\">.jsx<\/span>\n\u2502   \u2514\u2500\u2500 <span class=\"hljs-selector-tag\">routes<\/span>\n\u2502       \u2514\u2500\u2500 <span class=\"hljs-selector-tag\">index<\/span><span class=\"hljs-selector-class\">.jsx<\/span>\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">package-lock<\/span><span class=\"hljs-selector-class\">.json<\/span>\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">package<\/span><span class=\"hljs-selector-class\">.json<\/span>\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">public<\/span>\n\u2502   \u2514\u2500\u2500 <span class=\"hljs-selector-tag\">favicon<\/span><span class=\"hljs-selector-class\">.ico<\/span>\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">remix<\/span><span class=\"hljs-selector-class\">.config<\/span><span class=\"hljs-selector-class\">.js<\/span>\n\u2514\u2500\u2500 <span class=\"hljs-selector-tag\">jsconfig<\/span><span class=\"hljs-selector-class\">.json<\/span>\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\">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><code>app<\/code>: this folder is where all the Remix code goes\n<code>app\/entry.client.tsx<\/code>: this is the first file that runs when the app loads in the browser\n<code>app\/entry.server.tsx<\/code>: this is the first file that runs when a request is made to the server\n<code>app\/root.tsx<\/code>: this is the root of our remix application. Similar to <code>index.tsx<\/code> in Next\n<code>app\/routes<\/code> : just like Nextjs, this folder handles routing and pages\n<code>public<\/code> : this is where you put static files such as images, fonts, etc\n<code>remix.config.js<\/code>: similar to <code>next.config.js<\/code>, this has all Remix configurations<\/p>\n<p>Let\u2019s write our first Hello World program.\nNavigate to <code>app\/routes<\/code> and delete <code>index.jsx<\/code>. Go to <code>app\/root.jsx<\/code>, delete everything, and add these lines of code:<\/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\">\/\/app\/root.jsx<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { LiveReload } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@remix-run\/react\"<\/span>;\n  \n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">App<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n<span class=\"hljs-keyword\">return<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span> <span class=\"hljs-attr\">lang<\/span>=<span class=\"hljs-string\">\"en\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">meta<\/span> <span class=\"hljs-attr\">charSet<\/span>=<span class=\"hljs-string\">\"utf-8\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">title<\/span>&gt;<\/span>Remix Upload Images<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">title<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n    \n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n      Hello Remix world\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LiveReload<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span><\/span>\n);\n}\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<p>Here, <code>LiveReload<\/code> handles hot reload in Remix.<\/p>\n<p>Go ahead and run <code>npm run dev<\/code> in your terminal to serve the development build. You should get something like this:<\/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\">\n\u279c  remix-upload-image npm run dev\n    \n&gt; dev\n&gt; remix dev\n    \nWatching Remix app <span class=\"hljs-keyword\">in<\/span> development mode...\n\ud83d\udcbf Built <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-number\">736<\/span>ms\nRemix App Server started at http:<span class=\"hljs-comment\">\/\/localhost:3000 (http:\/\/192.168.80.200:3000)<\/span>\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<p><strong>Built in 736ms!<\/strong>\nNavigate to <code>http:\/\/localhost:3000<\/code> in your browser and you should see your hello world program.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_0F2ABAF94359EDEAF48DBF6566EBA1B537FD95B7C8E23921F91400E9254ECA0D_1657626236778_Screenshot+2022-07-12+at+12.43.21.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"793\"\/><\/p>\n<p>Let\u2019s go ahead to implementing the cloudinary image uploading functionalities.<\/p>\n<h2>Implementing the Image uploader<\/h2>\n<p>Firstly, we\u2019ll update our <code>app\/root.jsx<\/code> to look like this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">#root.jsx<\/span>\n\nimport {\n    Links,\n    LiveReload,\n    Meta,\n    Outlet,\n    Scripts,\n    ScrollRestoration,\n} from <span class=\"hljs-string\">\"@remix-run\/react\"<\/span>;\nimport stylesUrl from <span class=\"hljs-string\">\"~\/styles\/global.css\"<\/span>;\n    \nexport <span class=\"hljs-keyword\">const<\/span> links = () =&gt; {\n      <span class=\"hljs-keyword\">return<\/span> &#91;{ rel: <span class=\"hljs-string\">\"stylesheet\"<\/span>, href: stylesUrl }];\n    };\n\nexport <span class=\"hljs-keyword\">const<\/span> meta = () =&gt; ({\n      charset: <span class=\"hljs-string\">\"utf-8\"<\/span>,\n      title: <span class=\"hljs-string\">\"Remix Upload image\"<\/span>,\n      viewport: <span class=\"hljs-string\">\"width=device-width,initial-scale=1\"<\/span>,\n    });\n    \nexport <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">App<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    &lt;html lang=<span class=\"hljs-string\">\"en\"<\/span>&gt;\n      &lt;head&gt;\n        &lt;Meta \/&gt;\n        &lt;Links \/&gt;\n      &lt;\/head&gt;\n      &lt;body&gt;\n        &lt;Outlet \/&gt;\n        &lt;ScrollRestoration \/&gt;\n        &lt;Scripts \/&gt;\n        &lt;LiveReload \/&gt;\n      &lt;\/body&gt;\n    &lt;\/html&gt;\n  );\n}\n    \nexport <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ErrorBoundary<\/span><span class=\"hljs-params\">({ error })<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    &lt;div className=<span class=\"hljs-string\">\"error-container\"<\/span>&gt;\n      &lt;h1&gt;App Error&lt;\/h1&gt;\n      &lt;pre&gt;{error.message}&lt;\/pre&gt;\n    &lt;\/div&gt;\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\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here, we import components from the <code>@remix-run\/react<\/code> package and set an Error Boundary. Most important of those components are the <code>&lt;Links \/&gt;<\/code> and <code>&lt;Meta \/&gt;<\/code>. The <code>&lt;Link \/&gt;<\/code> component handles all <code>link<\/code> exports all through the app and the <code>&lt;Meta \/&gt;<\/code> component handles all meta exports on all routes while <code>&lt;Outlet \/&gt;<\/code> gives room for Children routes. Earlier, i mentioned that one of my favourite features of Remix is how it handles errors, well you can see how easy it is to create <strong>ErrorBoundaries.<\/strong> We have a <code>global.css<\/code>  ****file referenced in our code, let\u2019s create it now.\nCreate a <code>styles\/global.css<\/code> directory and add these lines of code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-comment\">\/* styles\/global.css *\/<\/span>\n    \n<span class=\"hljs-selector-tag\">body<\/span> {\n    <span class=\"hljs-attribute\">background-color<\/span>: <span class=\"hljs-number\">#000000<\/span>;\n    <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#ffffff<\/span>;\n    <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">1200px<\/span>;\n    <span class=\"hljs-attribute\">margin<\/span>: auto;\n    <span class=\"hljs-attribute\">font-family<\/span>: sans-serif;\n}\n    \n<span class=\"hljs-selector-class\">.error-container<\/span> {\n  <span class=\"hljs-attribute\">background-color<\/span>: <span class=\"hljs-built_in\">hsla<\/span>(<span class=\"hljs-number\">356<\/span>, <span class=\"hljs-number\">77%<\/span>, <span class=\"hljs-number\">59%<\/span>, <span class=\"hljs-number\">0.747<\/span>);\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">0.25rem<\/span>;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">0.5rem<\/span> <span class=\"hljs-number\">1rem<\/span>;\n}\n<span class=\"hljs-selector-tag\">a<\/span> {\n  <span class=\"hljs-attribute\">color<\/span>: wheat;\n  <span class=\"hljs-attribute\">text-decoration<\/span>: none;\n}\n    \n<span class=\"hljs-selector-tag\">a<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n    <span class=\"hljs-attribute\">text-decoration<\/span>: underline;\n    <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0.8<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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>This will be the styles used throughout our app.<\/p>\n<p>Let\u2019s go ahead to create our <strong>Index<\/strong> route. Create a <code>app\/route\/index.jsx<\/code> directory and add the following lines of code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">#app\/route\/index.jsx\n\nimport { Link } from \"@remix-run\/react\";\n\nexport default function Index() {\n  return (\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span> Remix image upload <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          This is a Remix app for uploading images to cloudinary\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">to<\/span>=<span class=\"hljs-string\">\"\/cloudinary-upload\"<\/span>&gt;<\/span> Upload Images here <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is going to be our <code>\/<\/code> route and page. Nothing much is going here except importing the <code>@remix-run\/react<\/code> <strong>Link.<\/strong> It represents the <code>&lt;a&gt;<\/code>  ****anchor tag and it\u2019s the main method of navigation in a remix app.<\/p>\n<p>Let\u2019s go ahead to create the main thing, the cloudinary upload route. Create a <code>cloudinary.jsx<\/code> in the <code>route<\/code> folder and add these lines of code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">#app\/route\/cloudinary.jsx<\/span>\n    \nimport {\n    json,\n    unstable_composeUploadHandlers <span class=\"hljs-keyword\">as<\/span> composeUploadHandlers,\n    unstable_createMemoryUploadHandler <span class=\"hljs-keyword\">as<\/span> createMemoryUploadHandler,\n    unstable_parseMultipartFormData <span class=\"hljs-keyword\">as<\/span> parseMultipartFormData,\n} from <span class=\"hljs-string\">\"@remix-run\/node\"<\/span>;\nimport { Form, useActionData } from <span class=\"hljs-string\">\"@remix-run\/react\"<\/span>;\nimport { uploadImage } from <span class=\"hljs-string\">\"~\/utils\/utils.server\"<\/span>;\nimport formStylesUrl from <span class=\"hljs-string\">\"~\/styles\/form.css\"<\/span>;\n\nexport <span class=\"hljs-keyword\">const<\/span> links = () =&gt; {\n    <span class=\"hljs-keyword\">return<\/span> &#91;{ rel: <span class=\"hljs-string\">\"stylesheet\"<\/span>, href: formStylesUrl }];\n};\n\nexport <span class=\"hljs-keyword\">const<\/span> action = async ({ request }) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> uploadHandler = composeUploadHandlers(\n        async ({ name, data }) =&gt; {\n            <span class=\"hljs-keyword\">if<\/span> (name !== <span class=\"hljs-string\">\"img\"<\/span>) {\n                <span class=\"hljs-keyword\">return<\/span> undefined\n            }\n            <span class=\"hljs-keyword\">const<\/span> uploadedImage = await uploadImage(data)\n            <span class=\"hljs-keyword\">return<\/span> uploadedImage.secure_url;\n        },\n        createMemoryUploadHandler()\n    );\n    \n    <span class=\"hljs-keyword\">const<\/span> formData = await parseMultipartFormData(request, uploadHandler);\n    <span class=\"hljs-keyword\">const<\/span> imgSource = formData.get(<span class=\"hljs-string\">\"img\"<\/span>);\n    <span class=\"hljs-keyword\">const<\/span> imgDescription = formData.get(<span class=\"hljs-string\">\"description\"<\/span>);\n    \n    <span class=\"hljs-keyword\">if<\/span> (!imgSource) {\n        <span class=\"hljs-keyword\">return<\/span> json({\n            error: <span class=\"hljs-string\">\"something is wrong\"<\/span>,\n        });\n    }\n    <span class=\"hljs-keyword\">return<\/span> json({\n        imgSource, imgDescription\n    });\n};\n    \nexport <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Index<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> data = useActionData();\n    <span class=\"hljs-keyword\">return<\/span> (\n        &lt;&gt;\n          &lt;Form method=<span class=\"hljs-string\">\"post\"<\/span> encType=<span class=\"hljs-string\">\"multipart\/form-data\"<\/span> id=<span class=\"hljs-string\">\"upload-form\"<\/span>&gt;\n            &lt;div&gt;\n              &lt;label htmlFor=<span class=\"hljs-string\">\"img\"<\/span>&gt; Image: &lt;\/label&gt;\n              &lt;input id=<span class=\"hljs-string\">\"img\"<\/span> type=<span class=\"hljs-string\">\"file\"<\/span> name=<span class=\"hljs-string\">\"img\"<\/span> accept=<span class=\"hljs-string\">\"image\/*\"<\/span> \/&gt;\n            &lt;\/div&gt;\n            &lt;div&gt;\n              &lt;label htmlFor=<span class=\"hljs-string\">\"description\"<\/span>&gt; Image description: &lt;\/label&gt;\n              &lt;input id=<span class=\"hljs-string\">\"description\"<\/span> type=<span class=\"hljs-string\">\"text\"<\/span> name=<span class=\"hljs-string\">\"description\"<\/span> \/&gt;\n            &lt;\/div&gt;\n            &lt;div&gt;\n              &lt;button type=<span class=\"hljs-string\">\"submit\"<\/span>&gt; Upload to Cloudinary &lt;\/button&gt;\n            &lt;\/div&gt;\n          &lt;\/Form&gt;\n        \n          {data?.errorMsg &amp;&amp; &lt;h3&gt;{data.errorMsg}&lt;\/h3&gt;}\n          {data?.imgSource &amp;&amp; (\n            &lt;&gt;\n              &lt;h2&gt;Uploaded Image: &lt;\/h2&gt;\n              &lt;img src={data.imgSource} alt={data.imgDescription || <span class=\"hljs-string\">\"Upload result\"<\/span>} \/&gt;\n                &lt;p&gt;{data.imgDescription}&lt;\/p&gt;\n            &lt;\/&gt;\n          )}\n        &lt;\/&gt;\n    )\n}\n\nexport <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ErrorBoundary<\/span><span class=\"hljs-params\">({ error })<\/span> <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> (\n        &lt;div className=<span class=\"hljs-string\">\"error-container\"<\/span>&gt;\n            &lt;pre&gt;{error.message}&lt;\/pre&gt;\n        &lt;\/div&gt;\n    );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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<p>Let\u2019s break this into snippets.<\/p>\n<p>Firstly, we imported some functions from the nodejs part of remix.<\/p>\n<ul>\n<li>\n<code>json<\/code> converts a response object to a JSON object,<\/li>\n<li>\n<code>unstable_composeUploadHandlers<\/code>: this is an upload handler that accepts our HTML input field <strong>name<\/strong> as parameter and file bytes from the uploaded images as <strong>data.<\/strong>\n<\/li>\n<li>\n<code>unstable_createMemoryUploadHandler<\/code>: this is another upload handler that stores streamed <code>multipart\/form-data<\/code>  parts in memory.<\/li>\n<\/ul>\n<p>These functions makes up our <code>uploadHandler<\/code> function. Before we leave the <code>uploadHandler<\/code> let\u2019s take a look at this code snippet:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/javascript<\/span>\n<span class=\"hljs-keyword\">const<\/span> uploadedImage = <span class=\"hljs-keyword\">await<\/span> uploadImage(data)\n<span class=\"hljs-keyword\">return<\/span> uploadedImage.secure_url;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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>The <code>uploadImage(data)<\/code>  takes in the image bytes as parameter, returns a Promise that resolves if our image has been saved to the cloudinary folder we\u2019ll specify soon and rejects if there\u2019s an error. If an image is uploaded successfully, it returns an object that looks like this for us to use:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\">{\n  <span class=\"hljs-attribute\">asset_id<\/span>: <span class=\"hljs-string\">'1c80a31297c4748b7d655190d1e5023b'<\/span>,\n  public_id: <span class=\"hljs-string\">'remixImages\/nrubhrrcv030zhiulzzb'<\/span>,\n  version: <span class=\"hljs-number\">1657796627<\/span>,\n  version_id: <span class=\"hljs-string\">'e78d675b8199040f471c89ce1903a8b0'<\/span>,\n  signature: <span class=\"hljs-string\">'f50122a9819e3458630afde2927dab615f05437c'<\/span>,\n  width: <span class=\"hljs-number\">1000<\/span>,\n  height: <span class=\"hljs-number\">1333<\/span>,\n  format: <span class=\"hljs-string\">'jpg'<\/span>,\n  resource_type: <span class=\"hljs-string\">'image'<\/span>,\n  created_at: <span class=\"hljs-string\">'2022-07-14T11:03:47Z'<\/span>,\n  tags: &#91;],\n  bytes: <span class=\"hljs-number\">204402<\/span>,\n  type: <span class=\"hljs-string\">'upload'<\/span>,\n  etag: <span class=\"hljs-string\">'ccd7e9f2ecda9e91de437ce15a9464b5'<\/span>,\n  placeholder: false,\n  url: <span class=\"hljs-string\">'http:\/\/res.cloudinary.com\/sammy365\/image\/upload\/v1657796627\/remixImages\/nrubhrrcv030zhiulzzb.jpg'<\/span>,\n  secure_url: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/sammy365\/image\/upload\/v1657796627\/remixImages\/nrubhrrcv030zhiulzzb.jpg'<\/span>,\n  folder: <span class=\"hljs-string\">'remixImages'<\/span>,\n  original_filename: <span class=\"hljs-string\">'file'<\/span>,\n  api_key: <span class=\"hljs-string\">'&lt;api-key&gt;'<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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>We\u2019ll be creating the <code>utils.server.js<\/code> very soon.\nLet\u2019s look at another code snippet here:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">await<\/span> parseMultipartFormData(request, uploadHandler);\n<span class=\"hljs-keyword\">const<\/span> imgSource = formData.get(<span class=\"hljs-string\">\"img\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> imgDescription = formData.get(<span class=\"hljs-string\">\"description\"<\/span>);\n\n<span class=\"hljs-keyword\">if<\/span> (!imgSource) {\n    <span class=\"hljs-keyword\">return<\/span> json({\n      <span class=\"hljs-attr\">error<\/span>: <span class=\"hljs-string\">\"something is wrong\"<\/span>,\n    });\n}\n<span class=\"hljs-keyword\">return<\/span> json({\n  imgSource, imgDescription\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><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>The <code>unstable_parseMultipartFormData<\/code> is a remix utility that handles multipart formdata file uploads instead of using <code>request.formData<\/code>. It returns the field value that our <code>uploadHandler<\/code>  returns. For instance, the value of <code>imgSource<\/code>  will the URL of the image uploaded to cloudinary.<br \/>\nFinally, all of this is wrapped by Remix actions. Remember what we said about Actions and loaders earlier, here we use actions. It\u2019s important that the <code>action({ request })<\/code> is an async function that comes before the loader and your template.\nSo, how do we access the data from our actions right?\n<code>const<\/code> <code>*data*<\/code> <code>=<\/code> <code>*useActionData*``();<\/code>\nThis line of code does that. The <code>useActionData()<\/code> hook returns the JSON parsed data from  our action.\nIf you\u2019ve understood through these parts, you\u2019re awesome! Let\u2019s create two files that we called in our route.\nFirst, create a <code>form.css<\/code> file in the <code>styles<\/code> directory and add these lines of css:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">#styles\/form.css<\/span>\n    \n<span class=\"hljs-comment\">#upload-form {<\/span>\n  width: <span class=\"hljs-number\">280<\/span>px;\n  margin: <span class=\"hljs-number\">5<\/span>rem auto;\n  background-color: <span class=\"hljs-comment\">#fcfcfc;<\/span>\n  padding: <span class=\"hljs-number\">20<\/span>px <span class=\"hljs-number\">50<\/span>px <span class=\"hljs-number\">40<\/span>px;\n  box-shadow: <span class=\"hljs-number\">1<\/span>px <span class=\"hljs-number\">4<\/span>px <span class=\"hljs-number\">10<\/span>px <span class=\"hljs-number\">1<\/span>px <span class=\"hljs-comment\">#aaa;<\/span>\n}\n<span class=\"hljs-comment\">#upload-form * {<\/span>\n    box-sizing: border-box;\n}\n<span class=\"hljs-comment\">#upload-form input {<\/span>\n  margin-bottom: <span class=\"hljs-number\">15<\/span>px;\n}\n<span class=\"hljs-comment\">#upload-form input&#91;type=text] {<\/span>\n  display: block;\n  height: <span class=\"hljs-number\">32<\/span>px;\n  padding: <span class=\"hljs-number\">6<\/span>px <span class=\"hljs-number\">16<\/span>px;\n  width: <span class=\"hljs-number\">100<\/span>%;\n  border: none;\n  background-color: <span class=\"hljs-comment\">#f3f3f3;<\/span>\n}\n<span class=\"hljs-comment\">#upload-form label {<\/span>\n  color: <span class=\"hljs-comment\">#777;<\/span>\n  font-size: <span class=\"hljs-number\">0.8<\/span>em;\n}\n<span class=\"hljs-comment\">#upload-form button&#91;type=submit] {<\/span>\n  display: block;\n  margin: <span class=\"hljs-number\">20<\/span>px auto <span class=\"hljs-number\">0<\/span>;\n  width: <span class=\"hljs-number\">150<\/span>px;\n  height: <span class=\"hljs-number\">40<\/span>px;\n  border-radius: <span class=\"hljs-number\">5<\/span>px;\n  border: none;\n  color: <span class=\"hljs-comment\">#eee;<\/span>\n  font-weight: <span class=\"hljs-number\">700<\/span>;\n  box-shadow: <span class=\"hljs-number\">1<\/span>px <span class=\"hljs-number\">4<\/span>px <span class=\"hljs-number\">10<\/span>px <span class=\"hljs-number\">1<\/span>px <span class=\"hljs-comment\">#aaa;<\/span>\n  background: <span class=\"hljs-comment\">#207cca; \/* Old browsers *\/<\/span>\n  background: -moz-linear-gradient(left, <span class=\"hljs-comment\">#207cca 0%, #9f58a3 100%); \/* FF3.6-15 *\/<\/span>\n  background: -webkit-linear-gradient(left, <span class=\"hljs-comment\">#207cca 0%,#9f58a3 100%); \/* Chrome10-25,Safari5.1-6 *\/<\/span>\n  background: linear-gradient(to right, <span class=\"hljs-comment\">#207cca 0%,#9f58a3 100%); \/* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ *\/<\/span>\n  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr=<span class=\"hljs-string\">'#207cca'<\/span>, endColorstr=<span class=\"hljs-string\">'#9f58a3'<\/span>,GradientType=<span class=\"hljs-number\">1<\/span> ); <span class=\"hljs-comment\">\/* IE6-9 *\/<\/span>\n}\nimg {\n  width: <span class=\"hljs-number\">100<\/span>%;\n}\ninput<span class=\"hljs-comment\">#img {<\/span>\n  color: red;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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<p>Another awesome thing about Remix is how it handles styles. When we navigate to the <code>cloudinary-upload<\/code> it loads this <code>form.css<\/code> file and when we leave the route, it unloads the styles. Take a second to imagine how that improves the overall site speed.<\/p>\n<p>Let\u2019s finally create the <code>utils\/util.server.js<\/code> directory. Add the following lines of code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ utils\/util.server.js<\/span>\n    \n<span class=\"hljs-keyword\">import<\/span> cloudinary <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { writeAsyncIterableToWritable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@remix-run\/node\"<\/span>;\n\ncloudinary.v2.config({\n    <span class=\"hljs-attr\">cloud_name<\/span>: process.env.CLOUD_NAME,\n    <span class=\"hljs-attr\">api_key<\/span>: process.env.API_KEY,\n    <span class=\"hljs-attr\">api_secret<\/span>: process.env.API_SECRET,\n});\n\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">uploadImage<\/span>(<span class=\"hljs-params\">data<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> uploadPromise = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-keyword\">async<\/span> (resolve, reject) =&gt; {\n        <span class=\"hljs-keyword\">const<\/span> uploadStream = cloudinary.v2.uploader.upload_stream(\n            { <span class=\"hljs-attr\">folder<\/span>: <span class=\"hljs-string\">\"remixImages\"<\/span> },\n            (error, result) =&gt; {\n                <span class=\"hljs-keyword\">if<\/span> (error) {\n                    reject(error)\n                    <span class=\"hljs-keyword\">return<\/span>;\n                }\n                resolve(result)\n            }\n        )\n        <span class=\"hljs-keyword\">await<\/span> writeAsyncIterableToWritable(data, uploadStream);\n    });\n        <span class=\"hljs-keyword\">return<\/span> uploadPromise;\n}\n\n<span class=\"hljs-keyword\">export<\/span> { uploadImage }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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>This is is the server side of Remix. The file above is a server module. It\u2019s best practice in Remix to handle all server side code in a <code>*.server.js<\/code> module, Remix also uses \u201ctree shaking\u201d to remove server code from browser bundles.\nOne last thing, create a <code>.env<\/code> file and add these details from your cloudinary dashboard:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ .env<\/span>\nCLOUD_NAME=cloud name here\nAPI_KEY= api key here\nAPI_SECRET= api secret here\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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>Now go ahead and run your development server with:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm run dev\n<\/code><\/span><\/pre>\n<p>You should be presented with these screens:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_0F2ABAF94359EDEAF48DBF6566EBA1B537FD95B7C8E23921F91400E9254ECA0D_1657799817525_Screenshot+2022-07-14+at+12.55.50.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1100\"\/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_0F2ABAF94359EDEAF48DBF6566EBA1B537FD95B7C8E23921F91400E9254ECA0D_1657799862069_Screenshot+2022-07-14+at+12.57.26.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1100\"\/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_0F2ABAF94359EDEAF48DBF6566EBA1B537FD95B7C8E23921F91400E9254ECA0D_1657799945145_screencapture-localhost-3000-cloudinary-upload-2022-07-14-12_58_10.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1490\" height=\"2160\"\/><\/p>\n<p>Go ahead to cloudinary and check our <strong>Media Uploads<\/strong> for the remixImage folder and our cat image.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this guide, we learned about Remix and it\u2019s top features, we also explored cloudinary and used it upload API to build a Remix app that enables users to upload images to Cloudinary.<\/p>\n<h2>Further Reading<\/h2>\n<ul>\n<li>\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/File\">Browser File API<\/a>\n<a href=\"https:\/\/remix.run\/docs\"><\/a>&#8211; <a href=\"https:\/\/remix.run\/docs\">Remix run documentation<\/a>\n<\/li>\n<\/ul>\n<p>Happy Coding!<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":27956,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,177,371],"class_list":["post-27955","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-javascript","tag-under-review"],"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>How to Upload Images to Cloudinary with Remix App<\/title>\n<meta name=\"description\" content=\"[Remix](https:\/\/remix.run\/) is the newest trending JavaScript framework in the ecosystem right now. It is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.\" \/>\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\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Upload Images to Cloudinary with Remix App\" \/>\n<meta property=\"og:description\" content=\"[Remix](https:\/\/remix.run\/) is the newest trending JavaScript framework in the ecosystem right now. It is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-08-12T07:28:47+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-02-22T20:29:32+00:00\" \/>\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\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"How to Upload Images to Cloudinary with Remix App\",\"datePublished\":\"2022-08-12T07:28:47+00:00\",\"dateModified\":\"2025-02-22T20:29:32+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/\"},\"wordCount\":9,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"Javascript\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/\",\"name\":\"How to Upload Images to Cloudinary with Remix App\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA\",\"datePublished\":\"2022-08-12T07:28:47+00:00\",\"dateModified\":\"2025-02-22T20:29:32+00:00\",\"description\":\"[Remix](https:\/\/remix.run\/) is the newest trending JavaScript framework in the ecosystem right now. It is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA\",\"width\":1490,\"height\":2160},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Upload Images to Cloudinary with Remix App\"}]},{\"@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\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"How to Upload Images to Cloudinary with Remix App","description":"[Remix](https:\/\/remix.run\/) is the newest trending JavaScript framework in the ecosystem right now. It is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.","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\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/","og_locale":"en_US","og_type":"article","og_title":"How to Upload Images to Cloudinary with Remix App","og_description":"[Remix](https:\/\/remix.run\/) is the newest trending JavaScript framework in the ecosystem right now. It is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-08-12T07:28:47+00:00","article_modified_time":"2025-02-22T20:29:32+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/"},"author":{"name":"","@id":""},"headline":"How to Upload Images to Cloudinary with Remix App","datePublished":"2022-08-12T07:28:47+00:00","dateModified":"2025-02-22T20:29:32+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/"},"wordCount":9,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA","keywords":["Guest Post","Image","Javascript","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/","name":"How to Upload Images to Cloudinary with Remix App","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA","datePublished":"2022-08-12T07:28:47+00:00","dateModified":"2025-02-22T20:29:32+00:00","description":"[Remix](https:\/\/remix.run\/) is the newest trending JavaScript framework in the ecosystem right now. It is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA","width":1490,"height":2160},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-upload-images-to-cloudinary-with-remix-app\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to Upload Images to Cloudinary with Remix App"}]},{"@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":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681922334\/Web_Assets\/blog\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081\/1b44ae4cb4915a1ecfaa9058ead7e0e623c76ca1-1490x2160-1_279567d081.png?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27955","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\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=27955"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27955\/revisions"}],"predecessor-version":[{"id":36939,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27955\/revisions\/36939"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/27956"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=27955"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=27955"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=27955"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}