{"id":39011,"date":"2025-11-05T07:00:00","date_gmt":"2025-11-05T15:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=39011"},"modified":"2026-02-19T15:34:39","modified_gmt":"2026-02-19T23:34:39","slug":"child-safe-platform-next-js-mediaflows","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows","title":{"rendered":"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Building online platforms for children comes with the responsibility of ensuring the content is safe to consume. To do so requires moderation, which, if manual, is slow, expensive, and simply can\u2019t keep up with the scale of modern user-generated content (UGC). A single inappropriate image slipping through the cracks can have serious consequences.<\/p>\n<p>In this guide, we\u2019ll build a powerful, automated safety pipeline for effective content moderation. We\u2019ll use the visual workflow builder <a href=\"https:\/\/cloudinary.com\/documentation\/mediaflows\"><strong>Cloudinary MediaFlows<\/strong><\/a> to instantly scan uploads for unsafe content, protect children\u2019s privacy by blurring their faces, and apply fun, kid-friendly enhancements. We\u2019ll then integrate this pipeline into a <strong>Next.js<\/strong> application, creating a secure frontend that only displays content <em>after<\/em> it has been officially approved.<\/p>\n<p>By the end, you\u2019ll have a fully functional application that not only moderates content but also provides a secure user experience.<\/p>\n<ul>\n<li>\n<strong>Live Demo:<\/strong> <a href=\"https:\/\/kidsafe-uploader-with-mediaflows.vercel.app\/\">kidsafe-uploader-with-mediaflows.vercel.app<\/a>\n<\/li>\n<li>\n<strong>GitHub Repo:<\/strong> <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\">github.com\/musebe\/kidsafe-uploader-with-mediaflows<\/a>\n<\/li>\n<\/ul>\n<h2>Prerequisites<\/h2>\n<p>Before we start, make sure you have the following:<\/p>\n<ul>\n<li>Node.js (v18 or later) installed.<\/li>\n<li>A free <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">Cloudinary account<\/a>.<\/li>\n<li>Basic knowledge of React and Next.js.<\/li>\n<\/ul>\n<p>Ready to build a safe online space for kids? Let\u2019s dive in.<\/p>\n<h2>Step 1: Setting Up the Next.js Foundation<\/h2>\n<p>First, we\u2019ll need a solid foundation. We\u2019ll spin up a new Next.js project and use the excellent <a href=\"https:\/\/ui.shadcn.com\/\"><code>shadcn\/ui<\/code><\/a> library to quickly build a clean, responsive interface.<\/p>\n<h3>1. Create the Next.js App<\/h3>\n<p>Open your terminal and run the <code>create-next-app<\/code> command. We\u2019ll be using the App Router, TypeScript, and Tailwind CSS.<\/p>\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\">npx create-next-app@latest kidsafe-uploader-<span class=\"hljs-keyword\">with<\/span>-mediaflows\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>Follow the prompts, accepting the defaults for a standard setup.<\/p>\n<blockquote>\n<p>You can view the full project structure on GitHub: <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\">kidsafe-uploader-with-mediaflows<\/a><\/p>\n<\/blockquote>\n<h3>2. Initialize <code>shadcn\/ui<\/code><\/h3>\n<p>Next, add <a href=\"https:\/\/github.com\/shadcn\/ui\"><code>shadcn\/ui<\/code><\/a> to handle component styling.<\/p>\n<p>This command configures <code>tailwind.config.ts<\/code> and global CSS automatically:<\/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-tag\">npx<\/span> <span class=\"hljs-selector-tag\">shadcn-ui<\/span><span class=\"hljs-keyword\">@latest<\/span> init\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>Accept the default options for all questions.<\/p>\n<h3>3. Build the Basic UI Layout<\/h3>\n<p>Let\u2019s create a centered card that will hold our upload widget.<\/p>\n<p>Add the <code>Card<\/code> component from <code>shadcn\/ui<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">shadcn-ui<\/span><span class=\"hljs-keyword\">@latest<\/span> add card\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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>Now, in <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/page.tsx\"><code>app\/page.tsx<\/code><\/a>, create a clean landing page layout.<\/p>\n<p>The core of the page is a <code>Card<\/code> component that centers our application.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\/\/ In app\/page.tsx\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex min-h-screen items-center justify-center\"<\/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\">\"w-full max-w-md\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Card<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CardHeader<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-center\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CardTitle<\/span>&gt;<\/span>Safe Media Uploader<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CardTitle<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CardDescription<\/span>&gt;<\/span>...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CardDescription<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CardHeader<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CardContent<\/span>&gt;<\/span>{\/* Uploader component will go here *\/}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CardContent<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Card<\/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\">main<\/span>&gt;<\/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\">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<blockquote>\n<p>You can view the full component file here: <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/page.tsx\"><code>app\/page.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<p>With this basic layout in place, we\u2019re ready to build the automated safety pipeline.<\/p>\n<h2>Step 2: Building the Visual Safety Pipeline With MediaFlows<\/h2>\n<p>This is where the magic happens. Instead of writing complex backend code, we\u2019ll use MediaFlows to create our entire safety and enhancement pipeline. It\u2019s like building with logic blocks.<\/p>\n<p>Navigate to <strong>MediaFlows<\/strong> in your Cloudinary dashboard and create a new flow. We\u2019ll add and configure blocks in sequence.<\/p>\n<h3>1. The Trigger: An Upload With a Tag<\/h3>\n<p>Every flow needs to know when to start. We\u2019ll trigger ours whenever an image is uploaded with a specific tag.<\/p>\n<ul>\n<li>\n<strong>Block.<\/strong> <code>File Upload<\/code>\n<\/li>\n<li>\n<strong>Configuration.<\/strong> Set it to trigger on assets with the tag <strong><code>moderation-queue<\/code><\/strong>. This tag is the key that connects our Next.js app to this specific workflow.<\/li>\n<\/ul>\n<h3>2. The Guard: <a href=\"https:\/\/cloudinary.com\/guides\/automations\/ai-content-moderation\">AI Content Moderation<\/a><\/h3>\n<p>Next, we add our automated safety check.<\/p>\n<ul>\n<li>\n<strong>Block.<\/strong> <code>Amazon Rekognition Moderation<\/code>\n<\/li>\n<li>\n<strong>Configuration.<\/strong> For a kid-safe platform, we need to be strict. Set the <strong>confidence level<\/strong> to a low value like <code>0.3<\/code> (30%) and add sensitive categories like <code>Suggestive<\/code>, <code>Explicit Nudity<\/code>, <code>Violence<\/code>, and <code>Visually Disturbing<\/code>.<\/li>\n<\/ul>\n<h3>3. The Fork in the Road: Conditional Logic<\/h3>\n<p>The moderation block gives us a result: <code>approved<\/code> or <code>rejected<\/code>. We need to create two different paths based on this outcome.<\/p>\n<ul>\n<li>\n<strong>Block.<\/strong> <code>If Condition<\/code>\n<\/li>\n<li>\n<strong>Configuration.<\/strong> Set it to check if the <code>moderation_status<\/code> from the previous step <strong>equals<\/strong> <code>rejected<\/code>. This creates a \u201cTrue\u201d path for unsafe images and a \u201cFalse\u201d path for safe ones.<\/li>\n<\/ul>\n<h3>4. The Quarantine: Handling Rejected Images<\/h3>\n<p>On the <strong>\u201cTrue\u201d<\/strong> path (if an image is rejected), we simply tag it for manual review and stop.<\/p>\n<ul>\n<li>\n<strong>Block.<\/strong> <code>Update Tags<\/code>\n<\/li>\n<li>\n<strong>Configuration.<\/strong> Set the action to <strong>Add<\/strong> the tag <strong><code>unsafe-content<\/code><\/strong>.<\/li>\n<\/ul>\n<h3>5. The Enhancement Factory: Processing Approved Images<\/h3>\n<p>On the <strong>\u201cFalse\u201d<\/strong> path, we\u2019ll run our approved images through a two-step enhancement process.<\/p>\n<ol>\n<li>\n<strong>Privacy First.<\/strong> Add an <code>Edit Media<\/code> block and configure it to apply the <strong><code>blur_faces<\/code><\/strong> effect.<\/li>\n<li>\n<strong>Kid-Friendly Fun.<\/strong> Add a second <code>Edit Media<\/code> block that takes the output from the first and applies the <strong><code>cartoonify<\/code><\/strong> effect.<\/li>\n<\/ol>\n<h3>6. The Final Stamp: Tagging the Processed Asset<\/h3>\n<p>Finally, we need to add a tag confirming that the image has passed our entire pipeline.<\/p>\n<ul>\n<li>\n<strong>Block.<\/strong> <code>Update Tags<\/code>\n<\/li>\n<li>\n<strong>Configuration.<\/strong> Add the tag <strong><code>safe-and-processed<\/code><\/strong> to the final, transformed image.<\/li>\n<\/ul>\n<p>After connecting all the blocks, your completed MediaFlow should look like this. Don\u2019t forget to <strong>Save and Enable<\/strong> the flow!<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764223846\/blog-Creating_a_Child-Safe_Platform_With_Next.js_and_Cloudinary_MediaFlows-1.png\" alt=\"MediaFlows workflow\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1919\" height=\"952\"\/><\/p>\n<p>With our automated backend now live, let\u2019s connect it to our Next.js application.<\/p>\n<h2>Step 3: Integrating the Cloudinary Uploader in Next.js<\/h2>\n<p>Now that the automated pipeline is ready in the cloud, it\u2019s time to connect your browser to your backend.<\/p>\n<p>We\u2019ll use <a href=\"https:\/\/www.npmjs.com\/package\/next-cloudinary\"><code>next-cloudinary<\/code><\/a> to add a pre-built upload widget to the app.<\/p>\n<h3>1. Configure Your Environment<\/h3>\n<p>Create a file named <code>.env.local<\/code> in your project root and add your Cloudinary credentials.<\/p>\n<p>You can find these on your Cloudinary Dashboard.<\/p>\n<pre class=\"js-syntax-highlighted\"><code>NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=&quot;your_cloud_name&quot;\nCLOUDINARY_API_KEY=&quot;your_api_key&quot;\nCLOUDINARY_API_SECRET=&quot;your_api_secret&quot;\n<\/code><\/pre>\n<p>You also need an <strong>Unsigned Upload Preset<\/strong> from your Cloudinary settings.<\/p>\n<p>Set the <strong>Folder<\/strong> to <code>kid-safe-platform<\/code> for better organization.<\/p>\n<h3>2. Build the Uploader Component<\/h3>\n<p>Create a new file at <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/components\/Uploader.tsx\"><code>components\/Uploader.tsx<\/code><\/a>.<\/p>\n<p>This will handle the upload logic on the client side.<\/p>\n<p>Install the package:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install next-cloudinary\n<\/code><\/span><\/pre>\n<p>Now, add the upload button code:<\/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-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { CldUploadButton } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-cloudinary\"<\/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\">Uploader<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldUploadButton<\/span>\n      <span class=\"hljs-attr\">uploadPreset<\/span>=<span class=\"hljs-string\">\"your_upload_preset_name\"<\/span>\n      <span class=\"hljs-attr\">options<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">tags:<\/span> &#91;\"<span class=\"hljs-attr\">moderation-queue<\/span>\"] }} \/\/ <span class=\"hljs-attr\">Triggers<\/span> <span class=\"hljs-attr\">the<\/span> <span class=\"hljs-attr\">MediaFlow<\/span>\n      <span class=\"hljs-attr\">onSuccess<\/span>=<span class=\"hljs-string\">{(result)<\/span> =&gt;<\/span> {\n        console.log(\"Upload successful!\", result);\n      }}\n    &gt;\n      {\/* Your custom button UI goes here *\/}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CldUploadButton<\/span>&gt;<\/span><\/span>\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<p>Next, import and place <code>&lt;Uploader \/&gt;<\/code> inside the <code>&lt;CardContent&gt;<\/code> section of<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/page.tsx\"><code>app\/page.tsx<\/code><\/a>.<\/p>\n<p>Run the app. When you upload an image, it will trigger the full moderation and enhancement pipeline you built in MediaFlows.<\/p>\n<p>You won\u2019t see the results yet, but the backend process is active.<\/p>\n<p><strong>See the full code on GitHub:<\/strong><\/p>\n<ul>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/components\/Uploader.tsx\">Uploader component<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/page.tsx\">Main page layout<\/a>\n<\/li>\n<\/ul>\n<h2>Step 4: Creating a Secure API to Verify Moderation<\/h2>\n<p>Your frontend can upload an image, but it doesn\u2019t know what your MediaFlow decides.<\/p>\n<p>Showing an image before it\u2019s approved is risky, since unsafe content could appear to users.<\/p>\n<p>We\u2019ll fix this by adding a secure API route that checks the final moderation result directly from Cloudinary.<\/p>\n<h3>1. Create the API Route<\/h3>\n<p>In your <code>app<\/code> directory, create a new file named <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/api\/check-status\/route.ts\"><code>app\/api\/check-status\/route.ts<\/code><\/a>.<\/p>\n<p>This server-side endpoint will use the Cloudinary <strong>Admin API<\/strong> to securely check moderation status.<\/p>\n<h3>2. Implement the Polling Logic<\/h3>\n<p>Inside <code>route.ts<\/code>, add a <code>POST<\/code> function that accepts a <code>public_id<\/code>.<\/p>\n<p>This function polls Cloudinary every few seconds until a final moderation tag appears.<\/p>\n<p>The loop ensures your app waits for the MediaFlow decision before showing the image.<\/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\">\/\/ In app\/api\/check-status\/route.ts<\/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 with your server-side API key and secret<\/span>\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_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\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">POST<\/span>(<span class=\"hljs-params\">request: Request<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { public_id } = <span class=\"hljs-keyword\">await<\/span> request.json();\n\n  <span class=\"hljs-comment\">\/\/ Poll Cloudinary up to 7 times<\/span>\n  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">let<\/span> i = <span class=\"hljs-number\">0<\/span>; i &lt; <span class=\"hljs-number\">7<\/span>; i++) {\n    <span class=\"hljs-keyword\">const<\/span> resource = <span class=\"hljs-keyword\">await<\/span> cloudinary.api.resource(public_id);\n    <span class=\"hljs-keyword\">const<\/span> tags = resource.tags || &#91;];\n\n    <span class=\"hljs-keyword\">if<\/span> (tags.includes(<span class=\"hljs-string\">\"safe-and-processed\"<\/span>)) {\n      <span class=\"hljs-keyword\">return<\/span> Response.json({ <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-string\">\"approved\"<\/span> });\n    }\n    <span class=\"hljs-keyword\">if<\/span> (tags.includes(<span class=\"hljs-string\">\"unsafe-content\"<\/span>)) {\n      <span class=\"hljs-keyword\">return<\/span> Response.json({ <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-string\">\"rejected\"<\/span> });\n    }\n\n    <span class=\"hljs-comment\">\/\/ Wait 3 seconds before checking again<\/span>\n    <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve<\/span>) =&gt;<\/span> setTimeout(resolve, <span class=\"hljs-number\">3000<\/span>));\n  }\n\n  <span class=\"hljs-comment\">\/\/ Time out after ~21 seconds<\/span>\n  <span class=\"hljs-keyword\">return<\/span> Response.json({ <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-string\">\"timeout\"<\/span> }, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">408<\/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<p>This endpoint acts as a <strong>secure verifier<\/strong>.<\/p>\n<p>It keeps the browser from guessing moderation results and ensures only approved images are displayed.<\/p>\n<p><strong>See the full code on GitHub:<\/strong><\/p>\n<ul>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/api\/check-status\/route.ts\">API Route: check-status<\/a>\n<\/li>\n<\/ul>\n<h2>Step 5: Building a Dynamic and Honest Frontend<\/h2>\n<p>Now that you have a secure API to verify moderation status, it\u2019s time to connect it to the frontend.<\/p>\n<p>We\u2019ll turn the uploader into an honest one that reflects the true image status, creating a safer user experience.<\/p>\n<h3>1. Manage UI States<\/h3>\n<p>We need to track the image\u2019s journey.<\/p>\n<p>Instead of just showing a preview, the UI will now have clear states:<\/p>\n<p><code>processing<\/code>, <code>approved<\/code>, <code>rejected<\/code>, and <code>error<\/code>.<\/p>\n<p>Use a <code>useState<\/code> hook in the <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/components\/Uploader.tsx\"><code>components\/Uploader.tsx<\/code><\/a> component.<\/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-string\">\"use client\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\ntype UploadStatus = <span class=\"hljs-string\">\"idle\"<\/span> | <span class=\"hljs-string\">\"processing\"<\/span> | <span class=\"hljs-string\">\"approved\"<\/span> | <span class=\"hljs-string\">\"rejected\"<\/span> | <span class=\"hljs-string\">\"error\"<\/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\">Uploader<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;status, setStatus] = useState &lt; UploadStatus &gt; <span class=\"hljs-string\">\"idle\"<\/span>;\n  <span class=\"hljs-comment\">\/\/ ...<\/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<h3>2. Call the Verification API<\/h3>\n<p>Create a function that calls the <code>\/api\/check-status<\/code> endpoint.<\/p>\n<p>This sends the <code>public_id<\/code> of the uploaded image and waits for the final verdict.<\/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-keyword\">const<\/span> checkModerationStatus = <span class=\"hljs-keyword\">async<\/span> (publicId: string) =&gt; {\n  setStatus(<span class=\"hljs-string\">\"processing\"<\/span>);\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/check-status\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">public_id<\/span>: publicId }),\n    });\n\n    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> response.json();\n    setStatus(data.status); <span class=\"hljs-comment\">\/\/ 'approved' or 'rejected'<\/span>\n  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n    setStatus(<span class=\"hljs-string\">\"error\"<\/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<p>Trigger this function from the <code>onSuccess<\/code> callback of <code>&lt;CldUploadButton \/&gt;<\/code>.<\/p>\n<p>It starts verification as soon as the upload finishes.<\/p>\n<h3>3. Render the UI Conditionally<\/h3>\n<p>Render different UI elements based on <code>status<\/code>.<\/p>\n<p>A <code>switch<\/code> statement makes it clear and simple.<\/p>\n<p>Show a loading message while processing, the cartoon-like image when approved, or a clear rejection message if unsafe.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" 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> renderStatusMessage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">switch<\/span> (status) {\n    <span class=\"hljs-keyword\">case<\/span> <span class=\"hljs-string\">'processing'<\/span>:\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>Analyzing for Safety...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>;\n    <span class=\"hljs-keyword\">case<\/span> <span class=\"hljs-string\">'approved'<\/span>:\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{...}<\/span> <span class=\"hljs-attr\">effects<\/span>=<span class=\"hljs-string\">{&#91;...]}<\/span> \/&gt;<\/span><\/span>; <span class=\"hljs-comment\">\/\/ Final safe image<\/span>\n    <span class=\"hljs-keyword\">case<\/span> <span class=\"hljs-string\">'rejected'<\/span>:\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-red-600\"<\/span>&gt;<\/span>Upload Rejected.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>;\n    <span class=\"hljs-keyword\">default<\/span>:\n      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n  }\n};\n\n<span class=\"hljs-keyword\">return<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldUploadButton<\/span> <span class=\"hljs-attr\">onSuccess<\/span>=<span class=\"hljs-string\">{...}<\/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\">\"mt-8\"<\/span>&gt;<\/span>{renderStatusMessage()}<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><\/span>\n);\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\">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>Your app now provides a truthful, secure experience.<\/p>\n<p>It waits for the backend\u2019s verdict before showing any content, ensuring unsafe images never appear.<\/p>\n<p><strong>See the full code on GitHub:<\/strong><\/p>\n<ul>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/components\/Uploader.tsx\">Uploader component<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/api\/check-status\/route.ts\">API Route<\/a>.<\/li>\n<\/ul>\n<h2>Step 6: Displaying the Curated Content<\/h2>\n<p>A moderation pipeline is only useful if it displays approved content.<\/p>\n<p>In this step, we\u2019ll create a gallery page that shows only images marked safe by MediaFlow.<\/p>\n<p>We\u2019ll use a <strong>Next.js Server Component<\/strong> for secure and efficient rendering.<\/p>\n<p>The server will fetch approved images from Cloudinary before sending the page to the browser.<\/p>\n<h3>1. Create the Server-Side Gallery Page<\/h3>\n<p>Create a new file at <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/gallery\/page.tsx\"><code>app\/gallery\/page.tsx<\/code><\/a>.<\/p>\n<p>This component connects securely to Cloudinary using your secret credentials, which stay safe on the server.<\/p>\n<p>We\u2019ll use a search expression that filters for images inside the <code>kid-safe-platform<\/code> folder <strong>and<\/strong> tagged with <code>safe-and-processed<\/code>.<\/p>\n<p>This ensures that only approved images appear in the gallery.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" 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> { 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 Cloudinary securely<\/span>\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_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\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GalleryPage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> searchResult = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">\"folder=kid-safe-platform AND tags=safe-and-processed\"<\/span>)\n    .sort_by(<span class=\"hljs-string\">\"created_at\"<\/span>, <span class=\"hljs-string\">\"desc\"<\/span>)\n    .execute();\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">GalleryGrid<\/span> <span class=\"hljs-attr\">resources<\/span>=<span class=\"hljs-string\">{searchResult.resources}<\/span> \/&gt;<\/span><\/span>;\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\">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>2. Build the Client-Side Display Grid<\/h3>\n<p>Create a new file at <a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/gallery\/GalleryGrid.tsx\"><code>app\/gallery\/GalleryGrid.tsx<\/code><\/a>.<\/p>\n<p>This component receives the list of approved images from the server and displays them in a responsive grid.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { CldImage } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-cloudinary\"<\/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\">GalleryGrid<\/span>(<span class=\"hljs-params\">{ resources }<\/span>) <\/span>{\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-2 md:grid-cols-4 gap-4\"<\/span>&gt;<\/span>\n      {resources.map((resource) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n          <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{resource.public_id}<\/span>\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{resource.public_id}<\/span>\n          <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"An approved and moderated image\"<\/span>\n          <span class=\"hljs-attr\">effects<\/span>=<span class=\"hljs-string\">{&#91;{<\/span> <span class=\"hljs-attr\">blurFaces:<\/span> <span class=\"hljs-attr\">true<\/span> }, { <span class=\"hljs-attr\">cartoonify:<\/span> <span class=\"hljs-attr\">true<\/span> }]}\n        \/&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-11\"><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 split between <strong>server fetching<\/strong> and <strong>client rendering<\/strong> is a best practice in modern Next.js apps.<\/p>\n<p>It keeps your API keys safe while giving users a fast and secure gallery experience.<\/p>\n<p><strong>See the full code on GitHub:<\/strong><\/p>\n<ul>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/gallery\/page.tsx\">Gallery Page<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/kidsafe-uploader-with-mediaflows\/blob\/main\/app\/gallery\/GalleryGrid.tsx\">Gallery Grid Component<\/a>\n<\/li>\n<\/ul>\n<h2>Conclusion and Next Steps<\/h2>\n<p>And there you have it! In just a few steps, we\u2019ve built a robust, fully <a href=\"https:\/\/cloudinary.com\/guides\/automations\/automated-content-moderation\">automated content moderation<\/a> pipeline. We\u2019ve combined the power of a modern web framework, <strong>Next.js<\/strong>, with the simplicity of a visual workflow builder, <strong>Cloudinary MediaFlows<\/strong>, to create a platform that enhances safety without compromising user experience.<\/p>\n<p>This project is a powerful testament to how modern, decoupled architecture can solve complex problems like content moderation in an elegant and scalable way.<\/p>\n<h2>Where to Go From Here?<\/h2>\n<p>This application is a fantastic foundation, but you can take it even further. Here are a few ideas:<\/p>\n<ul>\n<li>\n<strong>Switch to webhooks.<\/strong> Our API uses polling to check for status updates. For a more efficient, real-time solution, you could modify the MediaFlow to send a <strong>webhook<\/strong> to your API route upon completion, eliminating the need for repeated checks.<\/li>\n<li>\n<strong>Build a manual review dashboard.<\/strong> Create a private, admin-only page that fetches all images with the <code>unsafe-content<\/code> tag. This would give human moderators a dedicated queue to review and re-classify flagged content.<\/li>\n<li>\n<strong>Add more complex logic.<\/strong> Expand your MediaFlow to include other features, like adding a \u201csafe\u201d watermark to approved images or applying different effects based on AI-detected categories.<\/li>\n<li>\n<strong><a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up for a Cloudinary account<\/a>.<\/strong> Start building today with a free account.<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":39078,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[363,203,212,300],"class_list":["post-39011","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-media-flows","tag-moderation","tag-next-js","tag-user-generated-content"],"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>Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows<\/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\/child-safe-platform-next-js-mediaflows\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-05T15:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-19T23:34:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows\",\"datePublished\":\"2025-11-05T15:00:00+00:00\",\"dateModified\":\"2026-02-19T23:34:39+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows\"},\"wordCount\":10,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA\",\"keywords\":[\"MediaFlows\",\"Moderation\",\"Next.js\",\"User-Generated Content\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows\",\"url\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows\",\"name\":\"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA\",\"datePublished\":\"2025-11-05T15:00:00+00:00\",\"dateModified\":\"2026-02-19T23:34:39+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows\"}]},{\"@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":"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows","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\/child-safe-platform-next-js-mediaflows","og_locale":"en_US","og_type":"article","og_title":"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows","og_url":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows","og_site_name":"Cloudinary Blog","article_published_time":"2025-11-05T15:00:00+00:00","article_modified_time":"2026-02-19T23:34:39+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows","datePublished":"2025-11-05T15:00:00+00:00","dateModified":"2026-02-19T23:34:39+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows"},"wordCount":10,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA","keywords":["MediaFlows","Moderation","Next.js","User-Generated Content"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows","url":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows","name":"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA","datePublished":"2025-11-05T15:00:00+00:00","dateModified":"2026-02-19T23:34:39+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/child-safe-platform-next-js-mediaflows#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Creating a Child-Safe Platform With Next.js and Cloudinary MediaFlows"}]},{"@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\/v1761940425\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows\/Blog_Creating_a_Child-Safe_Platform_with_Next.js_and_Cloudinary_MediaFlows.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39011","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=39011"}],"version-history":[{"count":4,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39011\/revisions"}],"predecessor-version":[{"id":39820,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39011\/revisions\/39820"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/39078"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=39011"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=39011"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=39011"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}