-
Notifications
You must be signed in to change notification settings - Fork 27k
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
Add Uploadcare image loader #18688
Add Uploadcare image loader #18688
Changes from all commits
b743cbf
d7a0b99
cf1cf0a
50fe432
5a41cb2
bbd27f2
a8555b2
ae6beb3
08d098a
bb043e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
# vercel | ||
.vercel |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Uploadcare loader for Image Component Example | ||
|
||
This example shows how to use Uploadcare loader with [Image Component in Next.js](https://nextjs.org/docs/api-reference/next/image). | ||
|
||
The index page ([`pages/index.js`](pages/index.js)) has a couple images, one Uploadcare CDN hosted image and one external image. In [`next.config.js`](next.config.js), the `path` property is used to set Media Proxy endpoint. Run or deploy the app to see how it works! | ||
|
||
## Deploy your own | ||
|
||
Deploy the example using [Vercel](https://vercel.com): | ||
|
||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/image-component) | ||
|
||
## How to use | ||
|
||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: | ||
|
||
```bash | ||
npx create-next-app --example image-component-uploadcare image-app | ||
# or | ||
yarn create next-app --example image-component-uploadcare image-app | ||
``` | ||
|
||
Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
images: { | ||
loader: 'uploadcare', | ||
path: 'https://cd813a35e9a71d2f5125.ucr.io', // optional | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "image-component-uploadcare", | ||
"version": "1.0.0", | ||
"scripts": { | ||
"dev": "next dev", | ||
"build": "next build", | ||
"start": "next start" | ||
}, | ||
"dependencies": { | ||
"next": "latest", | ||
"react": "^16.13.1", | ||
"react-dom": "^16.13.1" | ||
}, | ||
"license": "MIT" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import styles from '../styles.module.css' | ||
import Image from 'next/image' | ||
|
||
const Code = (p) => <code className={styles.inlineCode} {...p} /> | ||
|
||
const Index = () => ( | ||
<div className={styles.container}> | ||
<div className={styles.card}> | ||
<h1>Uploadcare loader for Image Component</h1> | ||
<p> | ||
The following is an example of a reference to an image from the{' '} | ||
Uploadcare CDN at <Code>ucarecdn.com</Code> | ||
</p> | ||
<p> | ||
It will be served directly from <Code>ucarecdn.com</Code>, without | ||
proxying through Media Proxy. | ||
</p> | ||
<Image | ||
alt="Vercel logo" | ||
src="https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/vercel.png" | ||
width={1000} | ||
height={1000} | ||
/> | ||
<hr className={styles.hr} /> | ||
<p> | ||
The following is an example of a reference to an external image at{' '} | ||
<Code>assets.vercel.com</Code>. | ||
</p> | ||
<p>It will be proxied through Media Proxy.</p> | ||
<Image | ||
alt="Next.js logo" | ||
src="https://assets.vercel.com/image/upload/v1538361091/repositories/next-js/next-js.png" | ||
width={1200} | ||
height={400} | ||
/> | ||
<hr className={styles.hr} /> | ||
<p>SVGs and GIFs will be used without transformations</p> | ||
<Image | ||
alt="Next.js logo" | ||
src="https://ucarecdn.com/375bba4b-35db-4cb8-8fc7-7540625f2181/next.svg" | ||
width={64} | ||
height={64} | ||
/> | ||
<Image | ||
alt="Vercel logo" | ||
src="https://ucarecdn.com/0f23a269-13eb-4fc9-b378-86f224380d26/vercel.gif" | ||
width={64} | ||
height={64} | ||
/> | ||
<hr className={styles.hr} /> | ||
Checkout the documentation for{' '} | ||
<a href="https://nextjs.org/docs/basic-features/image-optimization#uploadcare-loader"> | ||
Image Optimization | ||
</a>{' '} | ||
to learn more. | ||
</div> | ||
</div> | ||
) | ||
|
||
export default Index |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
.container { | ||
padding: 4rem 1rem; | ||
font-family: -apple-system, BlinkMacSystemFont, sans-serif; | ||
} | ||
|
||
.container p { | ||
margin: 1.5rem 0; | ||
} | ||
|
||
.card { | ||
max-width: 50rem; | ||
box-shadow: -10px 10px 80px rgba(0, 0, 0, 0.12); | ||
border: 1px solid #eee; | ||
border-radius: 8px; | ||
padding: 2rem; | ||
margin: 0 auto; | ||
} | ||
|
||
.inlineCode { | ||
color: #be00ff; | ||
font-size: 16px; | ||
white-space: pre-wrap; | ||
} | ||
|
||
.inlineCode::before, | ||
.inlineCode::after { | ||
content: '`'; | ||
} | ||
|
||
.hr { | ||
border: 0; | ||
border-top: 1px solid #eaeaea; | ||
margin: 1.5rem 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ const loaders = new Map<LoaderValue, (props: LoaderProps) => string>([ | |
['imgix', imgixLoader], | ||
['cloudinary', cloudinaryLoader], | ||
['akamai', akamaiLoader], | ||
['uploadcare', uploadcareLoader], | ||
['default', defaultLoader], | ||
]) | ||
|
||
|
@@ -433,6 +434,68 @@ function cloudinaryLoader({ root, src, width, quality }: LoaderProps): string { | |
return `${root}${paramsString}${normalizeSrc(src)}` | ||
} | ||
|
||
function uploadcareLoader({ root, src, width, quality }: LoaderProps): string { | ||
const isOnCdn = /^https?:\/\/ucarecdn\.com/.test(src) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if i use custom cname like images.example.com for my uploadcare images? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's why we need ability to pass custom options directly to the image loader. I prefer not to change Image Loader API without discussion with Next.js team. So, custom cnames aren't supported now. I've added a few words about it in readme. |
||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
if (!isOnCdn && src.startsWith('/')) { | ||
throw new Error( | ||
`Failed to parse "${src}" in "next/image", Uploadcare loader doesn't support relative images` | ||
) | ||
} | ||
|
||
if (!isOnCdn && !/^https?:\/\/.+\.ucr\.io\/?$/.test(root)) { | ||
throw new Error( | ||
`Failed to parse "${root}" in "next/image", Uploadcare loader expects proxy endpoint like "https://YOUR_PUBLIC_KEY.ucr.io".` | ||
) | ||
} | ||
} | ||
|
||
const filename = src.substring(1 + src.lastIndexOf('/')) | ||
const extension = filename | ||
.toLowerCase() | ||
.split('?')[0] | ||
.split('#')[0] | ||
.split('.')[1] | ||
|
||
if (['svg', 'gif'].includes(extension)) { | ||
return isOnCdn ? src : `${root.replace(/\/$/, '')}${src}` | ||
} | ||
|
||
/** | ||
* Output image dimensions is limited to 3000px | ||
* It can be increased by explicitly setting /format/jpeg/ | ||
*/ | ||
const maxResizeWidth = Math.min(Math.max(width, 0), 3000) | ||
// Demo: https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/-/format/auto/-/resize/300x/vercel.png | ||
const params = ['format/auto', 'stretch/off', `resize/${maxResizeWidth}x`] | ||
|
||
if (quality) { | ||
/** | ||
* Uploadcare doesn't support integer-based quality modificators, | ||
* so we need to map them onto uploadcare's equivalents | ||
*/ | ||
const names = ['lightest', 'lighter', 'normal', 'better', 'best'] | ||
const intervals = [0, 38, 70, 80, 87, 100] | ||
const nameIdx = intervals.findIndex((min, idx) => { | ||
const max = intervals[idx + 1] | ||
return min <= quality && quality <= max | ||
}) | ||
params.push(`quality/${names[nameIdx]}`) | ||
} else { | ||
params.push('quality/smart') | ||
} | ||
|
||
const paramsString = '/-/' + params.join('/-/') + '/' | ||
|
||
if (isOnCdn) { | ||
const withoutFilename = src.slice(0, src.lastIndexOf('/')) | ||
return `${withoutFilename}${paramsString}${filename}` | ||
} | ||
|
||
return `${root.replace(/\/$/, '')}${paramsString}${src}` | ||
} | ||
|
||
function defaultLoader({ root, src, width, quality }: LoaderProps): string { | ||
if (process.env.NODE_ENV !== 'production') { | ||
const missingValues = [] | ||
|
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.
It would be nice to be able to pass custom options directly to the image loader. I could make another PR with this feature. What do you think?