App router question
Answered
Transvaal lion posted this in #help-forum
Transvaal lionOP
Can someone give me an idea of how I could do this with app router?
1. User uploads prompt on my site which is used as AI image prompt
2. Once the user submits the query, the website redirects to localhost:3000/i/[id] where id is generated, this page will show a loading component until the ai process is done
3. Once the ai process is done, the ai image is shown, if the user revists the page with the id, the ai image is shown immediately from redis db
How can I do this with app router?
1. User uploads prompt on my site which is used as AI image prompt
2. Once the user submits the query, the website redirects to localhost:3000/i/[id] where id is generated, this page will show a loading component until the ai process is done
3. Once the ai process is done, the ai image is shown, if the user revists the page with the id, the ai image is shown immediately from redis db
How can I do this with app router?
62 Replies
Transvaal lionOP
I tried thinking of something like this:
THe issue is the redirect doesn't allow the rest of the code to run
export async function generateLegoSet(formData: FormData) {
const id = short.generate();
redirect(`/l/${id}`);
noStore();
const userPrompt = formData.get("prompt");
const prompt = await generateLegoBoxPrompt(userPrompt?.toString()!);
const image = await openai.images.generate({
model: "dall-e-3",
prompt: prompt!,
style: "vivid",
});
const dalleImageUrl = image.data[0].url;
const uploadedImage = (await utapi.uploadFilesFromUrl([dalleImageUrl!]))[0]
.data!;
await kv.set(uploadedImage.name, uploadedImage.url);THe issue is the redirect doesn't allow the rest of the code to run
Transvaal lionOP
all this app router shit is making me go insane
how about
redirect after the process?Transvaal lionOP
that works but i want the user to be sent to the new url before the result is completely done
so that they can revisit it while it's still loading if that makes sense
that would need to implement event/task for that
Transvaal lionOP
ok thanks
at this point might just checke very second to see if the task has finished loading lol
@Transvaal lion at this point might just checke very second to see if the task has finished loading lol
maybe you could try this
export async function generateLegoSet(formData: FormData) {
const id = short.generate();
proccessImage(formData);
redirect(`/l/${id}`);
}
async function proccessImage(formData: FormData) {
const userPrompt = formData.get("prompt");
const prompt = await generateLegoBoxPrompt(userPrompt?.toString()!);
const image = await openai.images.generate({
model: "dall-e-3",
prompt: prompt!,
style: "vivid",
});
const dalleImageUrl = image.data[0].url;
const uploadedImage = (await utapi.uploadFilesFromUrl([dalleImageUrl!]))[0]
.data!;
await kv.set(uploadedImage.name, uploadedImage.url);
}@Ray maybe you could try this
ts
export async function generateLegoSet(formData: FormData) {
const id = short.generate();
proccessImage(formData);
redirect(`/l/${id}`);
}
async function proccessImage(formData: FormData) {
const userPrompt = formData.get("prompt");
const prompt = await generateLegoBoxPrompt(userPrompt?.toString()!);
const image = await openai.images.generate({
model: "dall-e-3",
prompt: prompt!,
style: "vivid",
});
const dalleImageUrl = image.data[0].url;
const uploadedImage = (await utapi.uploadFilesFromUrl([dalleImageUrl!]))[0]
.data!;
await kv.set(uploadedImage.name, uploadedImage.url);
}
Transvaal lionOP
Thank you but I get this now:
Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
The file that has the generateLegoSet function has "use server"
The component that is calling the function has "use client"
can you show the code where you call the server action?
Transvaal lionOP
sure
"use client";
import { generateLegoSet } from "../server/generate";
import Loading from "./loading";
import { useFormState } from "react-dom";
import React, { useEffect, useRef, useState } from "react";
import { GenerateButton } from "./generate-button";
const initialState = {
message: "",
};
export default function LegoForm() {
const [formState, formAction] = useFormState(generateLegoSet, initialState);
const submitRef = useRef<React.ElementRef<"button">>(null);
return (
<form action={formAction}>
<div className="my-8 flex h-14">
<input
type="text"
name="prompt"
id="prompt"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
submitRef.current?.click();
}
}}
className="p-2 block w-full rounded-md border-2 border-blue-500 outline-none bg-blue-50 mr-5 text-gray-900 shadow-sm ring-1 ring-inset ring-blue-300 placeholder:text-gray-400 focus:ring-2 focus:ring-blue-400 text-lg sm:leading-6"
placeholder="Tesla Cybertruck"
required
/>
<GenerateButton ref={submitRef} />
</div>
</form>
);
}This code has a type error too idk why
No overload matches this call.
Overload 1 of 2, '(action: (state: void) => void | Promise<void>, initialState: void, permalink?: string | undefined): [state: void, dispatch: () => void]', gave the following error.
Argument of type '(prevState: any, formData: FormData) => Promise<void>' is not assignable to parameter of type '(state: void) => void | Promise<void>'.
Target signature provides too few arguments. Expected 2 or more, but got 1.
Overload 2 of 2, '(action: (state: void, payload: FormData) => void | Promise<void>, initialState: void, permalink?: string | undefined): [state: void, dispatch: (payload: FormData) => void]', gave the following error.
Argument of type '{ message: string; }' is not assignable to parameter of type 'void'.ts(2769)Im new to this useFormState shit and honestly I don't understand it, if you have any good docs/guides lmk
well since you are doing redirect anyway, I don't think you need useFormState with it?
Transvaal lionOP
oh right
just put generateLegoSet in the action props
Transvaal lionOP
yeah ok
will try that thank you
Transvaal lionOP
So I still get the same error
⨯ Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
at stringify (<anonymous>)If I comment out the redirect the error disappears
"use client";
import { generateLegoSet } from "../server/generate";
import Loading from "./loading";
import { useFormState } from "react-dom";
import React, { useEffect, useRef, useState } from "react";
import { GenerateButton } from "./generate-button";
export default function LegoForm() {
const submitRef = useRef<React.ElementRef<"button">>(null);
return (
<form action={generateLegoSet}>
<div className="my-8 flex h-14">
<input
type="text"
name="prompt"
id="prompt"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
submitRef.current?.click();
}
}}
className="p-2 block w-full rounded-md border-2 border-blue-500 outline-none bg-blue-50 mr-5 text-gray-900 shadow-sm ring-1 ring-inset ring-blue-300 placeholder:text-gray-400 focus:ring-2 focus:ring-blue-400 text-lg sm:leading-6"
placeholder="Tesla Cybertruck"
required
/>
<GenerateButton ref={submitRef} />
</div>
</form>
);
}@Transvaal lion If I comment out the redirect the error disappears
how about this?
export async function generateLegoSet(formData: FormData) {
const id = short.generate();
//proccessImage(formData);
redirect(`/l/${id}`);
}that odd
Transvaal lionOP
I updated a bit of my code btw but I think it shoudnt change anything
export async function generateLegoSet(formData: FormData) {
const userPrompt = (formData.get("prompt") as string) ?? "";
if (!userPrompt) return;
console.log(userPrompt);
const id = short.generate();
// proccessImage(id, userPrompt);
redirect(`/l/${id}`);
}
async function proccessImage(id: string, userPrompt: string) {
try {
noStore();
const prompt = await generateLegoBoxPrompt(userPrompt);
const image = await openai.images.generate({
model: "dall-e-3",
prompt: prompt!,
style: "vivid",
});
const dalleImageUrl = image.data[0].url;
const uploadedImage = (await utapi.uploadFilesFromUrl([dalleImageUrl!]))[0]
.data!;
await kv.set(id, uploadedImage.url);
console.log(uploadedImage.url);
} catch (error) {
console.error(error);
return { message: "An error occurred, please try again." };
}
}where is redirect importing from?
Transvaal lionOP
import { redirect } from "next/navigation";hmm that should work
what version of next?
Transvaal lionOP
14.0.4
try restart the server?
Transvaal lionOP
Still errors
only comment out redirect works?
Transvaal lionOP
yea
maybe it's the l/[id] page
ah can you show the code?
Transvaal lionOP
yup it's the l/[id] page
oh ok
Transvaal lionOP
this works:
this doens't
import { getLegoSet } from "@/app/server/get";
import Loading from "@/app/ui/loading";
import Image from "next/image";
export default async function Page({ params }: { params: { id: string } }) {
return <p>hi</p>;
}this doens't
import { getLegoSet } from "@/app/server/get";
import Loading from "@/app/ui/loading";
import Image from "next/image";
export default async function Page({ params }: { params: { id: string } }) {
const id = params.id;
const legoSet = await getLegoSet(id);
return legoSet ? (
<Image
src={legoSet as string}
alt="Lego Image"
width={700}
height={700}
className="rounded-md"
/>
) : (
<Loading />
);
}getLegoSet:
import { kv } from "@vercel/kv";
export const runtime = "edge";
export async function getLegoSet(name: string) {
const imageUrl = await kv.get(name);
if (!imageUrl) {
return new Response("Not found", { status: 404 });
}
return imageUrl;
}ah
remove this
return new Response("Not found", { status: 404 });
and do this instead
const legoSet = await getLegoSet(id);
if (!legoSet) notFound()@Ray and do this instead
ts
const legoSet = await getLegoSet(id);
if (!legoSet) notFound()
Transvaal lionOP
TYSM EVERYHTHING WORKS
One last thing
Do you know if there is a better way to do this lol
"use client";
import { useEffect, useState } from "react";
import { getLegoSet } from "@/app/server/get";
import Loading from "@/app/ui/loading";
import Image from "next/image";
import { notFound } from "next/navigation";
export default function Page({ params }: { params: { id: string } }) {
const [legoSet, setLegoSet] = useState<string>("");
useEffect(() => {
const fetchLegoSet = async () => {
const data = await getLegoSet(params.id);
console.log(data);
if (!data) {
notFound();
} else if (data !== "loading") {
setLegoSet(data as string);
} else {
setTimeout(fetchLegoSet, 1000);
}
};
fetchLegoSet();
}, [params.id]);
if (!legoSet) {
return <Loading />;
}
return (
<Image
src={legoSet as string}
alt="Lego Image"
width={700}
height={700}
className="rounded-md"
/>
);
}Transvaal lionOP

alr
thank you
you can use swr with server action
Transvaal lionOP
alr ill take a look at this https://swr.vercel.app/docs/with-nextjs
export default function Page({ params }: { params: { id: string } }) {
const { data } = useSWR(params.id, async (id) => getLegoSet(id), { refreshInterval: 1000 });
if (!data) return <Loading />;
return (
<>
<Image
src={data as string}
alt="Lego Image"
width={700}
height={700}
className="rounded-md"
/>
</>
);
}Transvaal lionOP
Thank you so much man
It works
I spent hours trying to do this shit today and you helped me fix it in a coupel min
😆