-
Notifications
You must be signed in to change notification settings - Fork 460
Add openimg for image optimization #935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kentcdodds
merged 28 commits into
epicweb-dev:main
from
andrelandgraf:andrelandgraf/add-openimg
Feb 24, 2025
Merged
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
87d6e40
use openimg
andrelandgraf 64d45ff
add isAboveFold
andrelandgraf ec643c5
fmt and review comments
andrelandgraf 4d9eb4e
Update app/routes/users+/$username.tsx
andrelandgraf 3e848a4
Update app/routes/users+/index.tsx
andrelandgraf 5369c82
Update app/routes/users+/$username_+/notes.tsx
andrelandgraf 9fd9a73
Update app/routes/settings+/profile.index.tsx
andrelandgraf 3ae3947
Update app/routes/users+/$username_+/notes.$noteId.tsx
andrelandgraf f7aac86
Update app/routes/users+/$username_+/__note-editor.tsx
andrelandgraf cb47439
image docs & image optimization decision doc
andrelandgraf 3d1d0ad
wording changes
andrelandgraf 505685b
fix cache location
andrelandgraf d9dac31
update docs, add S3 URL to allowlist
andrelandgraf bc2f913
fix msw warnings and place the images not in a root dir during dev
kentcdodds 2dc20d5
this is what I want
kentcdodds 8cdd396
remove unnecessary note since we have tigris now
kentcdodds 6c26161
a few other updates
kentcdodds bc1dd11
openimg v0.2
andrelandgraf ecea2fd
latest openimg, fix types
andrelandgraf 8b68dca
use getSrc to create custom src string for bucket requests
andrelandgraf 2106862
support img download
andrelandgraf 990d8d0
clean up code
andrelandgraf 220c376
fix in openimg
andrelandgraf 1224f4d
reduce code by moving src processing
andrelandgraf c099b7f
Migrate to objectKey for image references across application
kentcdodds 45b5d3e
Add comment to getImgSrc explaining things
kentcdodds 6e9aa19
Remove redundant Content-Disposition header setting in images route
kentcdodds c73ba61
fix comment
kentcdodds File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { invariantResponse } from '@epic-web/invariant' | ||
| import { getImgResponse } from 'openimg/node' | ||
| import { prisma } from '#app/utils/db.server.ts' | ||
| import { getDomainUrl } from '#app/utils/misc.tsx' | ||
| import { getSignedGetRequestInfo } from '#app/utils/storage.server.ts' | ||
| import { type Route } from './+types/images.ts' | ||
|
|
||
| export async function loader({ request }: Route.LoaderArgs) { | ||
| const domain = getDomainUrl(request) | ||
| const headers = new Headers() | ||
| headers.set('Cache-Control', 'public, max-age=31536000, immutable') | ||
| return getImgResponse(request, { | ||
| headers, | ||
| allowlistedOrigins: [domain, process.env.AWS_ENDPOINT_URL_S3].filter( | ||
| Boolean, | ||
| ), | ||
| cacheFolder: | ||
| process.env.NODE_ENV === 'production' | ||
| ? '/data/images' | ||
| : './tests/fixtures/openimg', | ||
| getImgSource: ({ params }) => { | ||
| if (params.src.startsWith('/resources')) { | ||
| const searchParams = new URLSearchParams(params.src.split('?')[1]) | ||
| const userImageId = searchParams.get('userImageId') | ||
| if (userImageId) { | ||
| return handleUserImage(userImageId) | ||
| } | ||
| const noteImageId = searchParams.get('noteImageId') | ||
| if (noteImageId) { | ||
| return handleNoteImage(noteImageId) | ||
| } | ||
|
|
||
| // Fetch image from resource endpoint | ||
| return { | ||
| type: 'fetch', | ||
| url: domain + params.src, | ||
| } | ||
| } | ||
| if (URL.canParse(params.src)) { | ||
| // Fetch image from external URL; will be matched against allowlist | ||
| return { | ||
| type: 'fetch', | ||
| url: params.src, | ||
| } | ||
| } | ||
| // Retrieve image from filesystem (public folder) | ||
| if (params.src.startsWith('/assets')) { | ||
| // Files managed by Vite | ||
| return { | ||
| type: 'fs', | ||
| path: '.' + params.src, | ||
| } | ||
| } | ||
| // Fallback to files in public folder | ||
| return { | ||
| type: 'fs', | ||
| path: './public' + params.src, | ||
| } | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| async function handleUserImage(userImageId: string) { | ||
| const userImage = await prisma.userImage.findUnique({ | ||
| where: { id: userImageId }, | ||
| select: { objectKey: true }, | ||
| }) | ||
| invariantResponse(userImage, 'User image not found', { status: 404 }) | ||
|
|
||
| const { url, headers } = getSignedGetRequestInfo(userImage.objectKey) | ||
| return { | ||
| type: 'fetch', | ||
| url, | ||
| headers, | ||
| } | ||
| } | ||
|
|
||
| async function handleNoteImage(noteImageId: string) { | ||
| const noteImage = await prisma.noteImage.findUnique({ | ||
| where: { id: noteImageId }, | ||
| select: { objectKey: true }, | ||
| }) | ||
| invariantResponse(noteImage, 'Note image not found', { status: 404 }) | ||
|
|
||
| const { url, headers } = getSignedGetRequestInfo(noteImage.objectKey) | ||
| return { | ||
| type: 'fetch', | ||
| url, | ||
| headers, | ||
| } | ||
| } | ||
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # Introduce Image Optimization | ||
|
|
||
| Date: 2025-02-19 | ||
|
|
||
| Status: accepted | ||
|
|
||
| ## Context | ||
|
|
||
| As documented in [018-images.md](./018-images.md), the Epic Stack previously | ||
| didn't implement image optimization. Both static app images and dynamic user | ||
| images were served as is. However, optimizing images significantly improves web | ||
| performance by reducing both the browser load time and the byte size of each | ||
| image. On the other hand, one of the guiding principles of the Epic Stack is to | ||
| limit services (including the self-managed variety). A great middle ground is to | ||
| integrate a simple image optimization solution directly into the web server. | ||
| This allows each Epic Stack app to immediately utilize image optimization and | ||
| serve better web experiences without prescribing a service. | ||
|
|
||
| On-demand image optimization with a image optimization endpoint should be | ||
| sufficient for most applications and provide value right out of the gate. | ||
| However, it is also important that upgrading to a dedicated service shouldn't be | ||
| overly complicated and require a ton of changes. | ||
|
|
||
| ### Using openimg | ||
|
|
||
| The goal of openimg is to be easy to use but also highly configurable, so you | ||
| can reconfigure it (or replace it) as your app grows. We can start simple by | ||
| introducing a new image optimization endpoint and replace `img` elements with | ||
| the `Img` component. | ||
|
|
||
| ## Decision | ||
|
|
||
| Introduce an image optimization endpoint using the | ||
| [openimg package](https://github.com/andrelandgraf/openimg). We can then use the | ||
| `Img` component to query for optimized images and iterate from there. | ||
|
|
||
| ## Consequences | ||
|
|
||
| Serving newly added images will now lead to an image optimization step whenever | ||
| a cache miss happens. This increases the image laod time but greatly reduces the | ||
| images sizes. On further requests, the load time should also be improved due to | ||
| the decreased image sizes. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need the fetch request you make to include these headers.
I also need
getImgSourceto support returning a promise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Available via
openimgv0.2.0! 🎉