
Node.js in React with Server Actions with Next.js with Cloudinary
Node.js in React with Server Actions with Next.js with Cloudinary
Use Server Components and Server Actions to add image search to a Next.js app with the Cloudinary Node.js SDK.
Sign up for my newsletter: https://www.colbyfayock.com/newsletter/
---
# Resources
Tutorial: https://spacejelly.dev/posts/image-ga…
Code: https://github.com/colbyfayock/my-clo…
Demo: https://my-cloudinary-image-search.ve…
https://twitter.com/colbyfayock
https://twitch.tv/colbyfayock
https://www.colbyfayock.com/uses
#colbyfayock #nextjs #cloudinary #nextjs13 #webdevelopment
Content
0 -> one of my favorite ways of interacting
1.92 -> with cloudnary is using the node.js SDK
4.38 -> but how do you use node inside of a
6.24 -> react app well up until now you really
8.58 -> couldn't but the games changed here's
10.62 -> how we do it with server components
13.4 -> so we're going to use the node SDK in
16.02 -> order to build a little image gallery
17.46 -> where we're going to just list out all
18.72 -> the images inside of one of our accounts
20.64 -> where we're going to extend that by
22.38 -> giving it the ability to search such as
24.359 -> if I wanted to search space inside of
25.92 -> all my images we can see those results
27.779 -> all using the SDK now better yet we're
30.599 -> going to see how we can make a little
31.859 -> bit of magic happen using server actions
34.02 -> where we can submit that form for the
36 -> search query and push that through to
37.92 -> the server request in order to render
39.84 -> those different results based off that
41.52 -> search now to start up this project I
43.62 -> created a little skeleton here where I
45.3 -> have a search form I have a clear which
47.04 -> we're going to dynamically add depending
48.66 -> on if we have a query and we're going to
50.399 -> have a bunch of images listed out inside
51.96 -> of a grid in order to easily scaffold
54.059 -> this I used Tailwind elements which
55.92 -> allowed me to copy and paste some
57.3 -> different elements including my image
58.8 -> gallery itself as as well as a search
60.84 -> bar which has a few different options
62.34 -> that we can work with we can see that if
64.199 -> we look in the code I'm going to be
65.339 -> working out of page.tsx which is under
67.68 -> the root of the app directory which is
69.9 -> going to be our home page but we can see
71.58 -> that inside of the file there's really
73.02 -> nothing else in here right now except a
75.24 -> bunch of HTML and the class names for
77.159 -> the Tailwind now inside of my clouding
79.02 -> account I uploaded a bunch of images
80.46 -> already that I have from other different
82.02 -> projects but I'm going to particularly
83.64 -> work inside of my my image gallery
85.56 -> folder you can really work inside of
87.299 -> whatever folder or location that you
89.22 -> want but ultimately you're going to want
91.14 -> to have a list of different images so go
93 -> ahead and upload those to your account
94.259 -> but ultimately I want to take all these
95.939 -> and I want to add them onto my page and
98.159 -> to do that we're going to use the node
99.36 -> SDK which allows us to easily work with
101.46 -> cloudery inside of a node environment
103.02 -> including being able to do things like
104.759 -> manage and upload and really whatever
106.68 -> you need to do with your cloudinary
108.18 -> assets but to start off we're going to
110.04 -> want to install this SDK so we're going
112.02 -> to run npm install cloudinary right
114.18 -> inside of our terminal and once it's
115.86 -> done we can spin back up our development
117.24 -> server but once it's installed the first
119.04 -> thing we want to do is actually
119.88 -> configure cloudinery inside of our
121.979 -> project so I'm going to use this import
123.96 -> statement to import V2 as cloudinary and
126.78 -> I'm going to paste it in right at the
127.92 -> top of my project and then I need to
129.78 -> configure my instance of Cloud area so
131.58 -> I'm going to run a cloudery.config the
133.92 -> inside I'm going to want to add my cloud
135.959 -> name my API key and my API secret now
141.18 -> rather than writing these right inside
142.8 -> of the configuration I'm going to store
144.36 -> these as environment variables so inside
146.34 -> of the root of my project I'm going to
147.84 -> create a new file
149.48 -> called.env.local and inside I'm
151.379 -> ultimately going to want to create three
152.7 -> different variables I'm going to create
154.02 -> my cloudery cloud name my API key and my
156.599 -> API secret and if we'll notice that I'm
158.7 -> appending next public to my cloud name
160.92 -> because later as we'll see we want to
162.84 -> have that accessible inside of the
164.459 -> application which could also be
166.08 -> client-side so we want to make sure that
168 -> this is available to use throughout the
169.98 -> app but in order to get those values we
171.72 -> can navigate over to our programmable
173.28 -> media dashboard where here we're going
175.2 -> to be able to see our Cloud name our API
177 -> key and our API secret so we can just
178.92 -> start copying and pasting these values
180.48 -> in including the cloud name and all the
182.64 -> other keys to ultimately make sure we
184.5 -> have all of our environment variables
185.879 -> set and once those are set we can head
187.62 -> back into page.tsx where we can
189.72 -> configure each of those in order to grab
191.879 -> it from the environment variable file so
193.62 -> at this point we're ready to actually
194.76 -> start using the SDK where we have a
196.92 -> couple options for how we get all of our
198.599 -> resources including the admin API where
201.18 -> we can actually hit the resources
202.379 -> endpoint but we're going to actually use
204.06 -> the search API because we're going to
205.86 -> eventually add a search to this now if
207.84 -> we actually go down to the search method
209.34 -> we can see that we can do this by easily
211.2 -> running cloudinery.search we can then
213.54 -> execute that command which we can then
215.159 -> use as a promise to get those results so
217.8 -> at the top of my home component I'm
219.12 -> going to set up my results which is
220.92 -> going to be equal to awaitlinary dot
223.62 -> search we're going to also pass in
225.48 -> expression which for now we're going to
227.519 -> just leave blank but then we're going to
228.959 -> run execute at the end of that which is
231.239 -> going to trigger that search but as we
232.98 -> can see we actually have a little
234.12 -> squiggly line here and that's because
235.44 -> we're currently trying to use a weight
236.94 -> inside of a normal function where what
239.58 -> we want want to do is we want to set up
241.319 -> our home component to actually be in a
243.84 -> synchronous function that way we can use
245.7 -> that nice async await syntax but to see
248.34 -> how this works let's actually console
249.9 -> log out the results and if we refresh
252.12 -> the page and ignore some of these errors
253.799 -> where this is just because I must have
255.239 -> not have updated the SVG that I was
256.979 -> pasting in but what will actually not
259.56 -> see is the console log and that's
261.78 -> intentional as we're not running that
264.06 -> console log in the client we're running
265.8 -> that on the server as this is a server
268.139 -> component so what we need to actually do
270.18 -> is look inside of our terminal where we
272.58 -> should now be able to see our asset data
274.8 -> from making that search but that means
277.139 -> we do have all that image data where we
279.6 -> can now start to add that to the page
281.04 -> now to start I want to destructure the
282.78 -> resources just to make it a little bit
284.04 -> easier for me to access so let's
286.62 -> destructure our results to resources
289.08 -> where now when once we have these
291.6 -> resources we're going to scroll down
293.16 -> until we get to where we want to display
295.02 -> our images where particularly I'm going
297.24 -> to add my image inside of each and every
299.1 -> one of these little divs that I got from
301.259 -> that Tailwind elements so I'm going to
302.88 -> say
303.62 -> resources.map and then for each free
305.699 -> source I'm going to ultimately return
307.82 -> one of these divs and I can also go
311.46 -> ahead and get rid of those other divs
313.86 -> since I don't need them anymore but then
316.44 -> I can create a new image tag
319.44 -> inside of that where my source is going
321.479 -> to be resource dot secure your url I'm
326.699 -> going to have an ALT which I'm just
328.62 -> going to leave blank for now since we
329.759 -> don't really have an appropriate way of
332.16 -> adding that
333.36 -> where you want to make sure that we have
335.34 -> our width which is going to be
337.46 -> resource.width as well as our height and
340.979 -> then we also see that we're going to get
342.24 -> a little typescript error and I'm not
344.16 -> going to go in too deep into typescript
345.9 -> with this so I'm just going to say
347.639 -> object since we're ultimately or I
350.52 -> probably want to just say any so I don't
352.44 -> have to worry about this as it probably
354.78 -> should be an object anyways we have our
357 -> resource where we're going to be able to
358.38 -> get all that information now if we
359.94 -> reload the page we can start to see all
361.62 -> those resources are now showing up on my
363.66 -> page but these aren't necessarily the
365.34 -> ones that I was looking for remember I
366.96 -> wanted to show a folder which might or
368.759 -> might not be your use case but I want to
370.62 -> show a folder because I like to try to
372 -> keep all my assets organized so this is
374.16 -> where we can start to work with the
375.6 -> actual expression and form our search
378.3 -> query for working with the search
380.16 -> endpoint so what I'm going to first do
381.78 -> is I'm going to add folder and I'm going
383.699 -> to set that to my image
385.979 -> Gallery where as soon as the page
388.08 -> reloads we can see that I get a lot of
389.58 -> those nice images right on the page now
391.5 -> one thing when working with these images
393 -> directly from our calendar account is
394.919 -> it's going to give us those raw images
396.419 -> which is fine because that's ultimately
398.34 -> what we're requesting but if we start to
400.139 -> look at these images this one for
401.699 -> instance is six thousand by four
403.44 -> thousand wide that's because this is
405.419 -> just a bunch of unsplash images that I
406.979 -> dumped inside of my account but
408.6 -> realistically we're not going to have
410.1 -> the images inside of our account at the
411.96 -> exact size that we want which is where
414 -> the power of cloudery comes in where we
415.62 -> can start to dynamically transform our
417.419 -> images to only the sizes that we need
419.46 -> now we could probably use the node SDK
422.039 -> and add some transformations to our
423.66 -> images that way which is certainly a
425.34 -> good way if you want to just stick with
426.539 -> that but instead we're going to use next
428.34 -> cloudinary which is a way that we can
430.199 -> get some first class support for some
431.94 -> amazing plenary features we're inside an
433.74 -> xjs so to get started we want to npm
436.199 -> install next Cloud energy so I'm going
437.94 -> to run that command inside of my
439.139 -> terminal but as we can see under
440.52 -> configuration we need an environment
442.259 -> variable but we already set this one up
443.88 -> because as I mentioned before we were
445.56 -> going to want to have that next public
446.94 -> cloudinary Cloud name in order to access
448.86 -> that using client components now this
451.38 -> Library comes with a few different
452.52 -> features in order to make it easy to
454.139 -> work with Cloud energy inside an xjs
455.639 -> including an upload widget and a video
457.5 -> player but we're going to stick to the
459.18 -> CLD image component which is going to
460.919 -> allow us to just take some advantage of
462.599 -> some of those image transformation
464.16 -> features so we're going to first import
466.08 -> CLD image from next Cloud Mary so I'm
468.479 -> going to paste that in at the top of my
469.86 -> file and we can see in order to use CLD
471.96 -> image we have a few required parameters
474.18 -> including width height source and alts
476.88 -> where sizes is optional but of course we
478.919 -> all always want to have responsive
480.24 -> sizing but really all we need to do is
482.46 -> change this image tag to CLD image now
485.699 -> once we refresh the page though we're
487.139 -> going to see an error where we're
488.639 -> currently trying to use a client
490.319 -> component inside of a server component
493.02 -> and I want to make sure that that is
495.12 -> clear where we have that separation of a
497.46 -> server component and a client component
499.68 -> now in order to use client components
502.02 -> inside of next.js all we really need to
503.94 -> do is add use client to the the top of
506.58 -> the file but I don't want to do that
508.08 -> inside of my home page that's going to
509.639 -> turn this into a client component which
511.319 -> first of all I don't want to do I want
513.18 -> to be able to use the node SDK inside of
515.279 -> a server component so what we're going
516.839 -> to do is we're going to create a wrapper
518.459 -> around the CLD image component so inside
520.979 -> of a new folder called components I'm
523.5 -> going to create a new file called CLD
526.339 -> image.tsx inside I'm going to start it
529.08 -> off by adding that use client directive
531.24 -> or I'm going to create a new constant
532.68 -> called CLD image and I'm going to just
535.14 -> scaffold out a new component
538.14 -> where I'm going to ultimately export
540.72 -> that default CLD image but inside is
544.26 -> where I'm going to return that CLD image
546.48 -> component so at the top I'm going to cut
549.18 -> out that import statement I'm going to
551.94 -> import this as next CLD image just so
555.18 -> that I can have it nice for organization
557.519 -> and not actually have a conflicting name
560.22 -> there but I'm going to ultimately return
561.54 -> that component I'm going to take the
563.459 -> props from CLD image I'm going to spread
566.399 -> those out onto that next CLD image
569.82 -> component now if you notice we're going
571.56 -> to get a typescript issue here and
573.42 -> there's probably better ways to solve
574.98 -> this but again I'm just going to escape
576.42 -> into using any but then we can see that
578.94 -> we're going to be able to pass in any
580.62 -> prop that we want into the actual CLD
583.68 -> image component now to use this I'm
585.66 -> going to import CLD image from my
588.959 -> components directory where I'm going to
590.58 -> grab CLD image and I'm going to make
592.74 -> sure that I have that capital I now I'm
595.019 -> realizing the reason why I'm getting the
596.459 -> squiggly line is because I have to
597.779 -> actually access this from the root so
599.94 -> it's actually app slash component CLD
602.1 -> image but as we can see we're already
603.72 -> referencing that CLD image so we don't
605.459 -> actually have to make any changes but
607.2 -> once the page reloads we can start to
608.7 -> see our images trickle in and they might
610.86 -> be a little slow to load for the first
612.12 -> time because we're loading new URLs and
614.279 -> if you're loading huge huge huge images
616.019 -> like I am of course they need to process
618.12 -> fast a little bit slower the first time
619.98 -> but as soon as I reload the page we can
621.6 -> see they start to trickle in fast even
623.459 -> though these are huge images now if I
625.62 -> look back inside of the image and I
627.54 -> start to inspect it we can still see
629.58 -> that I'm loading these images at huge
631.92 -> sizes that are completely unnecessary
634.2 -> for my project such as we can see this
636.66 -> one image here it's rendered size is 158
639.959 -> by 158 yeah I'm loading it at 383 840
644.64 -> pixels by 3848 pixels anyways it's super
648.42 -> huge way more than I need so what can we
650.7 -> do to actually serve it at the size we
652.2 -> need now there's a few ways I can do
654 -> this such as adding the sizes attribute
655.92 -> where if I add since it's four column
658.019 -> let's add 25 viewport width which isn't
660.899 -> going to be exact but it works for our
662.339 -> purpose once the page reloads and we
664.26 -> look at that intrinsic size we can see
666.12 -> that it's at a lot smaller of a size and
669.18 -> we can see that the source set has all
671.04 -> those different sizes depending on the
673.26 -> size of the viewport now one thing we
675.36 -> didn't look at is the performance of all
677.04 -> this so let's revert this back to image
679.019 -> and I'm going to also comment out the
680.519 -> sizes where if I'm loading all these
682.56 -> images as is I'm currently loading 27.6
685.98 -> megabytes of images that's an incredibly
688.92 -> huge amount and completely unnecessary
690.839 -> now starting to reverse back through
693 -> that reversing I guess let's now switch
695.76 -> that back to the CLD image component yet
697.98 -> not add that size is prop yet now once
700.56 -> we refresh the page again we're going to
702.12 -> start to see a few things including the
703.8 -> type is going to be avif for all those
705.839 -> images if you're inside of a monochrome
708.18 -> browser where it's going to
709.56 -> automatically optimize the images and
711.839 -> automatically deliver the most modern
713.16 -> format for that browser so by just
716.1 -> simply changing it to CLD image alone
717.959 -> we're now cutting that down to 15.5
720.54 -> megabytes and we're still serving them
722.76 -> at those huge resolution sizes but now
726.06 -> again let's enable that sizes attribute
728.339 -> which is going to give us the responsive
729.66 -> sizes we're going to completely cut that
731.579 -> down even more where now we're going to
733.74 -> be serving it in that Abiff format where
735.42 -> it makes sense but now we've taken that
737.279 -> down all the way to 389 kilobytes that's
740.519 -> a huge transformation and so much less
743.1 -> that we're needing our our users to
745.62 -> actually download when they're visiting
747.54 -> our site but I think my excitement here
750 -> got ahead of myself so let's actually
751.44 -> get back to the use case here where we
753.42 -> want to make a search on these actual
755.04 -> images right now the way that cloud
756.54 -> running search is going to work is it's
757.86 -> going to take a few things into
758.94 -> consideration where it's definitely
760.68 -> possible that when we're searching on
762.3 -> these these image files are going to
763.8 -> just have an ID or Garbage as the actual
766.62 -> image name but one thing that I have on
768.54 -> all my images is I auto tag them using
770.82 -> the Google auto tagging add-on so that I
773.339 -> have everything that's included inside
774.839 -> of that using AI now if you already have
776.94 -> your images inside of cloudner you can
778.56 -> always check out this Auto Tags feature
780.54 -> which you can use the image analysis
782.399 -> using that Google auto tagging add-on
784.74 -> and apply those for each of every every
787.139 -> one of your images but you can also do
788.94 -> this programmatically using the SDK but
791.459 -> now let's actually dive into building
792.72 -> that search where we're going to use
794.04 -> next.js server actions which is just to
796.38 -> be clear an alpha feature that's built
798.48 -> on top of react actions but we can see
800.7 -> here in this example we're going to
802.38 -> really just Define a function we're
803.82 -> going to make sure that we mark it as
805.74 -> we're using the server but then we can
808.019 -> use the server methods like the node STK
812.16 -> in order to make different requests or
814.32 -> perform different actions now ultimately
816.18 -> the way that works is we're going to
817.44 -> pass in that function as the action onto
820.5 -> a form element which is going to allow
822.48 -> us to actually invoke that action now
824.459 -> inside of my project the way that I'm
825.839 -> going to handle this is I have a form
827.579 -> where I have my search query input and
830.04 -> I'm going to add an action to that form
831.779 -> and let's call it search where then I'm
834.06 -> going to create my new async
837.06 -> function called search and inside is
840.12 -> where I'm going to perform that search
841.56 -> action now I'm not going to actually
843.48 -> make that search request inside of
845.579 -> search instead what I'm going to do is
847.44 -> I'm going to grab the form data from
849.42 -> this form I'm going to grab that query
851.88 -> I'm going to just append it as a
853.8 -> redirect to the page so that it's going
856.26 -> to revalidate that page it's going to
858.54 -> then use that to hit the expression that
861.54 -> I'm going to configure here and make the
863.339 -> search that way that way it's a little
864.959 -> bit more linear for how I'm going to
866.639 -> actually make this search request so
868.62 -> let's first set this up using use server
871.32 -> but then the way that we're going to get
873.18 -> our form data is the first parameter is
875.94 -> going to be this data object which is
878.22 -> ultimately going to be typed as form
879.779 -> data so what we can do is we can get the
881.82 -> query by running data.get where we get
884.639 -> it where we're going to grab that query
886.199 -> key from the form data now ultimately we
889.199 -> want to pass that into the URL so that
891.12 -> we can revalidate the page to trigger
893.16 -> that search request to do that we're
895.199 -> going to import the redirect method from
898.8 -> next navigation and once we have this
901.62 -> query data we're going to run that
903.36 -> redirect and we're going to just simply
904.92 -> stay on the page that we're on now
906.18 -> unless of course you want to redirect
907.74 -> this to a search page for instance and
909.72 -> I'm going to add a query parameter where
912.6 -> I'm going to pass in that data query
914.579 -> value now one final thing that we need
916.74 -> to do in order to take advantage of
918.3 -> these server actions is actually opt
920.339 -> into the server actions as I mentioned
922.74 -> this is still an alpha feature so we
924.899 -> want to make sure that we add this
926.16 -> experimental feature to our next config
927.899 -> so inside of our next config we're going
930.06 -> to set up the experimental property
933.18 -> we're inside we're going to say server
935.04 -> actions we're going to set that to true
936.959 -> but now when we try to load this page
939.18 -> and let's search for something like
940.92 -> rocket we can see that the page
943.26 -> refreshed now we currently show the same
945.42 -> results because we haven't tweaked that
946.86 -> search request at all but we now see
948.54 -> that query parameter in the URL so let's
950.519 -> actually start to build this expression
951.899 -> so I'm going to say let expression is
954.36 -> equal to and we're going to start off
956.1 -> with this folder of my gallery
959.88 -> so let's set that as a string I'm going
961.56 -> to then pass in that expression as the
963.48 -> value but we ultimately want to say if
965.639 -> we have a query which we're going to set
967.68 -> up in a second we want to append to this
970.56 -> expression we're going to say expression
972.72 -> is equal to expression and we're going
975.36 -> to say and we're going to say our search
978.36 -> of query now the nice thing with using
981 -> server components we can pretty easily
982.74 -> get that query parameter so the first
985.199 -> argument of our home props is going to
988.26 -> give us the search params which we need
991.139 -> to make sure we type this and let's call
993.06 -> this an object and search params I'm
996 -> just going to call this any for now but
997.259 -> inside the next.js documentation you can
999.3 -> find a better way to actually type this
1000.92 -> out but now we can say inside of search
1002.899 -> params which is just going to be an
1004.639 -> object we can say that this query
1006.8 -> constant query is equal to search
1009.1 -> params.query I'm going to move that
1010.94 -> above just for organizational sake but
1013.1 -> just to reiterate what's happening we're
1014.6 -> grabbing this query from our search
1016.339 -> params which we appended using that
1018.68 -> server action now once we have that
1020.779 -> we're going to append it dynamically to
1023.12 -> our expression our search expression
1025.04 -> which we're going to pass into that
1026.6 -> culinary search request and we can
1028.339 -> already see once the page updates that
1029.959 -> it has that rocket now but let's go back
1032 -> to the home page and if we make a new
1033.559 -> search such as what if I search for a
1035.36 -> guitar we're going to be able to see
1037.04 -> that we get that image with a guitar or
1039.26 -> if we search for something else like
1040.64 -> rocket again we get the rocket if we
1042.62 -> search for cat because I guess it didn't
1045.079 -> detect that image that's not going to
1046.339 -> show up but if I search for space we see
1048.5 -> all the images that kind of look like
1049.7 -> space even though this base jellyfish is
1051.919 -> that a space jellyfish it might be but
1053.78 -> we get the images that kind of deal with
1055.4 -> space now another method that we can use
1057.38 -> for these server actions is the clear
1059.059 -> where if we have a query and only if we
1061.4 -> have a query we want to be able to clear
1063.38 -> that query so first of all we're going
1065.36 -> to take that query and we're going to
1066.74 -> scroll down and if we have it we're or
1070.16 -> rather if we yeah if we have a query
1073.28 -> that's the only time we're going to
1075.26 -> actually show that clear button but on
1077.72 -> this form where we hold that clear we're
1080.419 -> going to add another action of clear
1082.4 -> where I'm going to just duplicate this
1084.5 -> search one I'm going to call it clear
1085.94 -> and but this time we're not going to use
1088.1 -> that form data we're just going to
1089.419 -> Simply send it back to that root page so
1092.179 -> now once I hit clear we can see it goes
1094.58 -> back to the initial page now this is a
1097.28 -> simple use case of server actions this
1099.08 -> might not even be the best use case for
1100.88 -> using this kind of functionality but
1102.74 -> it's a simple way to start to illustrate
1104.419 -> what's actually happening in that
1106.22 -> process of using the server actions and
1108.62 -> again I do want to make sure that it's
1109.94 -> clear that these are an alpha feature so
1112.34 -> it's super prone to breakage but it's
1114.14 -> fun to start to explore what different
1115.46 -> apis that we're going to have available
1116.9 -> and ultimately the fact that now that we
1119.419 -> have these server components it's much
1121.4 -> easier to be able to use different sdks
1123.62 -> like the node SDK for clownery in order
1126.26 -> to easily grab our assets manage our
1128.419 -> assets or do whatever we want to do
1129.799 -> inside of that same react environment
1132.38 -> that we know and love there's so much
1134.12 -> functionality to explore in the new app
1135.679 -> router that I feel like I'm really only
1136.94 -> scratching the surface what what's your
1138.98 -> favorite part of the new app router
1140.299 -> features is it server actions is it
1141.799 -> something else let me know in the
1142.82 -> comments if you want to explore other
1144.559 -> new app router features check out my
1146.36 -> video where I show you how to create a
1147.98 -> site map an RSS feed and even a static
1150.44 -> Json file using the static route
1152.179 -> handlers and that first class sitemap
1153.919 -> support and if you like this video make
1155.84 -> sure you hit thumbs up subscribe and
1157.52 -> click that little notification Bell for
1158.96 -> future web dev content thanks for
1160.7 -> watching
Source: https://www.youtube.com/watch?v=kK-XtyDuUD4