Create Thumbor image url on server
Unanswered
Pekingese posted this in #help-forum
PekingeseOP
Hi there!
In my app I want to handle images using Thumbor URL scheme (https://thumbor.readthedocs.io/en/latest/usage.html#image-endpoint). Thumbor can be used with a key/token for more security.
Obviously this key should not be exposed to the client so I created a non-public environment variable. Now I'm struggling rendering the URL with this key without convert the non-public env into a public one.
I chose thumbor-js-url-builder (https://github.com/heysafronov/thumbor-url-builder) to render the URL. For testing purposes I created a function to return the Thumbor URL with my key as public env. This works so far but is client side rendered. It looks something like this:
Now I tried to figure out how I can run this function server side. I read about Server Actions but I'm not sure if this is the right was to go. Anyway I tried out with a simple function like this
After insert "use server" and the feature flag in my config I got an error wich says
Is this a good approach anyway or do you recommend something else?
In my app I want to handle images using Thumbor URL scheme (https://thumbor.readthedocs.io/en/latest/usage.html#image-endpoint). Thumbor can be used with a key/token for more security.
Obviously this key should not be exposed to the client so I created a non-public environment variable. Now I'm struggling rendering the URL with this key without convert the non-public env into a public one.
I chose thumbor-js-url-builder (https://github.com/heysafronov/thumbor-url-builder) to render the URL. For testing purposes I created a function to return the Thumbor URL with my key as public env. This works so far but is client side rendered. It looks something like this:
import Thumbor from 'thumbor-js-url-builder';
export const getImagorImageUrl = (
imagorSecret, baseUrl, imageSrc
) => {
const thumbor = new Thumbor(imagorSecret, baseUrl);
thumbor.setImagePath(imageSrc);
// some more Thumbor configs
return thumbor.buildUrl();
}Now I tried to figure out how I can run this function server side. I read about Server Actions but I'm not sure if this is the right was to go. Anyway I tried out with a simple function like this
"use server"
export async function myAction() {
const imagorSecretKeyEnv = process.env.IMAGOR_SECRET;
return await "Hallo";
}After insert "use server" and the feature flag in my config I got an error wich says
Module not found: Can't resolve '@vercel/turbopack-ecmascript-runtime/dev/client/hmr-client.ts'. What did I do wrong?Is this a good approach anyway or do you recommend something else?
86 Replies
are you using turbopack?
PekingeseOP
Not as I know. The error is called from "myAction" but I have no import wich could include this package.
Turbopack is part of Next itself.
check the script in your packages.json
turbopack is not enable by default
Can't resolve '@vercel/turbopack-ecmascript-runtime/dev/client/hmr-client.ts' from the error message, it should be coming from turbopackPekingeseOP
I see. I changed my dev script to
next dev --turbo but then I had to remove the feature flag from my config. And now I run into a 504 Gateway Time-outit was next dev?
PekingeseOP
Yes
what the feature flag did you add to nextconfig?
serverAction?
PekingeseOP
Yes
what version of next are you using?
PekingeseOP
13.5.4
and how do you execute the
myAction?PekingeseOP
Like this
const url = myAction();
console.log(url);is it client component?
and you import it?
PekingeseOP
Yes. I guess this would be the problem as this actions are normally used by forms, right?
Yes, importing it like this
import { myAction } from "@/pages/api/actions";yes form or other event
PekingeseOP
That's another reason I'm not sure if this is the right approach
previous version have issue when importing the server action to client component with third party package
can you render the url on server side?
PekingeseOP
I'm sorry, I'm not a native speaker so I'm not sure if I get you correctly. Do you mean if I could create the URL on server and fetch the final url via request?
are you using app router?
I think you can just do this in the server component?
<img src={getImagorImageUrl(imagorSecret, baseUrl, imageSrc)} />PekingeseOP
Ah, ok. So maybe I just need to define the whole component as server component. But I guess this would lead to the same error as I need to declare it with "use server"?
no, with app router, every component is server component
unless you put
'use client' on topyou don't even need to use server action here
PekingeseOP
Actually I do have the "use client" on top, that's the issue.
@Ray can you render the url on server side?
that's why i ask you can you render the url on server side 😆
PekingeseOP
Ok, now I get it 😅
either render a list of url and pass it down to client component or move
'use client' to somewhere elsePekingeseOP
Sadly I need the "use client" on top as I'm using components from another React application and without this I always run into an error (React is undefined).
The part with the secret key is differently for each image. It's an hmac including the key and some other informations of the image itself. So I can't prerender urls. But maybe I should render the URL in backend and fetch it in my image component to prevent the key to get public.
Thank's for your help and ideas!
try create a image route handler, not sure if this work in build tho
export function GET() {
const url = getImagorImageUrl(...)
return fetch(url)
}btw, is your page static or dynamic?
PekingeseOP
Ok, I'll try. The page is static.
ok, I just tried and it work
PekingeseOP
I still need to declare getImagorImageUrl as "use server" to be able to use the non-public env, right?
no need
just make sure the env doesn't have
NEXT_PUBLIC prefix'use server' is for server action onlyPekingeseOP
Ok, thanks!
let me know if it works for you
PekingeseOP
It does not work. I can't access the non-public env in getImagorImageUrl
why?
getImagorImageUrl is executing in route handler
PekingeseOP
I don't know, but my URL is in unsafe mode that means the key is undefined
your img tag should be
<img src='/api/images' />PekingeseOP
I tried with the route handler but does not work. I already use API routes. In the docs it says I can't mix them.
what do you mean by mix them?
PekingeseOP
Use both, route handler and API routes.
Maybe it's not a no go, but it says:
Good to know: Route Handlers are only available inside the app directory. They are the equivalent of API Routes inside the pages directory meaning you do not need to use API Routes and Route Handlers together.
you were using api route?
both should work, just use the one you are using
PekingeseOP
Yes
Ok, I try. Thanks.
ah a little bit tricky with api route
route handler would be easier
it should be fine to use both as long as the path does not conflict
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const url = getImagorImageUrl(...)
const result = await fetch(url);
const buffer = Buffer.from(await result.arrayBuffer());
res.setHeader(
"Content-Type",
result.headers.get("Content-Type") ?? "image/jpg"
);
return res.send(buffer);
}PekingeseOP
Thanks for the code.
I finally got the problem. I tried both, route handler and api routes, and both were ignored. I can't use routes in my app because it is merged with another CMS wich handles the whole routing.So I need to figure out how to use them in my setting, this needs some further research. I'm sorry I didn't get earlier to this point. It's the first time I work with this
oh you using static export?
PekingeseOP
In the config the output is defined as 'standalone'
then it should be a server
how the cms handler the routing?
PekingeseOP
Actually I'm not sure yet. In the docs it says it fetches dynamically but the methods are called loadStaticPaths and loadStaticProps. I will talk to the contributors to get more clarity.
loadStaticPaths and loadStaticProps are Thumbor things? never heard that
PekingeseOP
No, it's part of this merge solution to fetch props in Next.js.
never heard it
PekingeseOP
No problem. I will talk to the contributors and figure out if Next routing could be enabled or better fix this using an api to the backend.
Thank you so much for your help. I'm a huge step further now.
PekingeseOP
Hi @Ray
I managed to use the route handler. That worked. I just struggle with handling the response. The way you showed me using the route directly in the img tag works
I managed to use the route handler. That worked. I just struggle with handling the response. The way you showed me using the route directly in the img tag works
<img src='api/images' />, but as I want to render a srcset too I need the url of my images as string to be able to use them in the src attribute but also create my srcset.I fetch my url like this
So far after fetching I set my response in a React state und use the value from there. But maybe there is another way to handle the response?
async function getImagorSrc(width) {
const response = await fetch("/api/images?src=someUrl&width=1400");
return response.json();
}
getImagorSrc(mainWidth)
.then((image) => {
// how to use my result as string here?
})So far after fetching I set my response in a React state und use the value from there. But maybe there is another way to handle the response?
For my srcset I also used a state and try to concat my different urls but this does not work well, it only sets one size in my state if I do not refresh my page. To be sure not to lead in an infinite loop, I wrapped all this in useEffect. This lookes like this:
Is this a good way to go or would there be some better approach?
useEffect(() => {
const mainWidth = 580;
const srcsetWidths = [
1440,
1150,
900,
580
];
async function getImagorSrc(width) {
const response = await fetch("/api/images?src=" + node.properties.image.src + "&width=" + width);
return response.json();
}
getImagorSrc(mainWidth)
.then((image) => {
setImgMainSrc(image)
})
// here comes the loop for my srcset sizes
for (let i = 0; i < srcsetWidths.length; i++) {
getImagorSrc(srcsetWidths[i])
.then((image) => {
setImgSrcset(imgSrcset + image + ' ' + srcsetWidths[i] + 'w,')
})
}
}, [])Is this a good way to go or would there be some better approach?
you can use
https://nextjs.org/docs/app/api-reference/components/image#loader
<Image /> component with custom loader https://nextjs.org/docs/app/api-reference/components/image#loader
PekingeseOP
Looks good, but I have to use frontend components from another package built in React. Therefore I can't use the Next image component and have to provide the srcset as a string
You may want to have a look to the source code of
<Image /> component and build your ownPekingeseOP
Ok, thanks!