payload
payload copied to clipboard
feat: dynamic image resize
Description
This PR exposes an express route for all collections with upload configuration, which will return the image resized from the parameters from the query string.
The motivation for this change is to allow for a frontend to query for sizes that are not known beforehand and defined as named crops in the collection config.
Implemented in this PR
-
[x] Express route
/resize/:id
for all collections with upload configuration. Query parameterswidth
,height
,position
,fit
,background
are passed to the image resizer and the resulting image is returned. e.g:/api/{collectionSlug}/resize/{id}?width=160&height=100&position=centre&fit=cover&background=white
-
[x] It accounts for the
disableLocalStorage
option in the upload config.-
If
false
: The image is loaded from disk, resized and returned. -
If
true
: The image is pulled from theurl
property, resized and returned. This only works if consumer has configured aurl
property, e.g. from an afterRead hook. This is the same property that is used by the payload dashboard to show the image correctly.import { AfterReadHook } from 'payload/dist/collections/config/types'; const afterRead: AfterReadHook[] = [ ({ doc }) => { doc.url = `https://.../${doc.filename}`; }, ];
-
-
[x] I have read and understand the CONTRIBUTING.md document in this repository
Type of change
- [x] New feature (non-breaking change which adds functionality)
- [x] This change requires a documentation update
Checklist:
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
While I was working on my payload-webp plugin I was thinking of implementing this function as well, but then I stopped because of concerns of misuse of such function - anyone could spam a server with resizing requests resulting in cpu/mem/queue issues.
Did you consider the risks? If it's going to be merged maybe there could be placed some ways to prevent some at least like request limits, generate new size only if resolution is significantly different from any existing ones, verify origin (for example allow resize only when origin is one of CSFR domains), etc...
I'm thinking of similiar optimalization of the pre-set image sizes flow by not generating particular size until it is requested for the first time. Potentially saving huge amounts of disk space.
Hi @chladog Those are some good points about the security which I had not considered. I like your suggestions & perhaps this should be enabled via a flag in the collection config?
I agree that it makes sense to compute the pre-set image sizes when requested instead of at upload time so that you are able to add or change them in the future without requiring the images to be re-uploaded & processed.
Then you would be able to request a named crop like:
/api/{collectionSlug}/resize/{id}?crop=my_pre_defined_crop_name
If it saves the resized image to disk, then it will also prevent computation for subsequent requests.
I also thought it would be useful if the editor could configure a focal point in the GUI that the resizing would use as a centre point.
Just dropping my two cents here that this feature and especially the focal point idea would be phenomenal!
The fixed sizes are pretty limiting for something like a Hero Image, where the needed size is influenced directly by the client viewport width.
Adding focal point, as well as crop/flip/rotation, would be a game changer over other headless cms
Agreed, crops and focal points would be awesome for sure.
A different way to solve the security point is using HMAC, similar to for example Thumbor (HMAC-SHA256 should be used instead of HMAC-SHA1 though.) or Imaginary. This way, fully dynamic options are still an option. However this still isn't a way to have safe client influenced resizing.
Hey all, the team and I have revisited this specific feature and have landed on the following thoughts:
- We are going to be revisiting our usage of Sharp entirely, and potentially abstracting it out into a separate package where its usage would be opt-in. Sharp is a big dependency, and lots of frontends now use third-party image manipulation libraries like Cloudinary or
next/image
to transform image sizes on-the-fly in a better way. - If / when we remove Sharp from our dependencies, we will need to refactor this PR to support the "opt-in" Sharp method.
- This functionality could be added via plugin, as an additional endpoint for each upload-enabled collection. The plugin could expose its own suite of rate-limiting / protection mechanisms wherein the author could allow only requests from certain origins / requests with specific API keys / etc. to be considered valid. This would solve the bad-actor scenarios that we discussed above.
@alexbechmann what do you think about converting this to a plugin? And / or exposing options to allow users to leverage certain authentication / access mechanisms like API keys or whitelisted origin domains?
Looking forward to hearing what you think. In the interim, we are doing a bit of PR cleanup and I will close this one for now.
Once we make Sharp an opt-in dependency, I will revisit this and we can determine if this functionality does indeed belong in core. 👍