Manual static image optimization
parent
11aed6e30f
commit
c74fad1793
@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
|
||||
import styles from '~/styles/form.module.css'
|
||||
|
||||
const submitUrl = 'https://contact.tempest.dev/api/contact/me'
|
||||
import { useState, useRef, FormEvent } from 'react'
|
||||
|
||||
export default function ContactForm() {
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [status, setStatus] = useState('')
|
||||
|
||||
const nameRef = useRef<HTMLInputElement>()
|
||||
const emailRef = useRef<HTMLInputElement>()
|
||||
const messageRef = useRef<HTMLTextAreaElement>()
|
||||
|
||||
const submit = async (ev: FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault()
|
||||
|
||||
setStatus('')
|
||||
|
||||
const name = nameRef.current.value
|
||||
const email = emailRef.current.value
|
||||
const message = messageRef.current.value
|
||||
|
||||
if (!name) setStatus(s => s + ' Name required.')
|
||||
if (!email) setStatus(s => s + ' Email required.')
|
||||
if (!message) setStatus(s => s + ' Message required.')
|
||||
|
||||
if (!name || !email || !message)
|
||||
return
|
||||
|
||||
setSubmitting(true)
|
||||
|
||||
try {
|
||||
|
||||
await fetch(submitUrl, {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name, email, message
|
||||
})
|
||||
})
|
||||
|
||||
setStatus('Message sent successfully')
|
||||
} catch {
|
||||
setStatus('Error submitting message, please try again')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={styles.form} onSubmit={submit}>
|
||||
<label htmlFor="name">Name:</label>
|
||||
<input disabled={submitting} name="name" ref={nameRef} />
|
||||
|
||||
<label htmlFor="email">Email:</label>
|
||||
<input disabled={submitting} name="email" ref={emailRef} />
|
||||
|
||||
<label htmlFor="message">Message:</label>
|
||||
<textarea disabled={submitting} name="message" ref={messageRef} />
|
||||
|
||||
<button disabled={submitting} type="submit">Submit</button>
|
||||
{status && <span className={styles.status}>{status}</span>}
|
||||
</form>
|
||||
)
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { ImgHTMLAttributes } from 'react'
|
||||
import sharp from 'sharp'
|
||||
|
||||
const outputDir = path.join(process.cwd(), '.next/static/images')
|
||||
const serverPath = '/_next/static/images/'
|
||||
|
||||
interface ReqProps {
|
||||
src: string,
|
||||
alt: string,
|
||||
}
|
||||
|
||||
type FixedSingleDimension = {width: number} | {height: number}
|
||||
type FixedBothDimension = {width: number, height: number}
|
||||
type MultiSize = {width: number[]} | {height: number[]}
|
||||
type SizeProps = FixedSingleDimension | FixedBothDimension | MultiSize
|
||||
type ImageAttrs = Omit<ImgHTMLAttributes<HTMLImageElement>, "width" | "height" | "src">
|
||||
|
||||
type ImageProps = ImageAttrs
|
||||
& SizeProps
|
||||
& ReqProps
|
||||
|
||||
interface ProcessedImage {
|
||||
hostPath: string,
|
||||
randPrefix: string,
|
||||
widths: {[width: number] : string}
|
||||
}
|
||||
|
||||
async function ImageServer({src,...imgAttrs}: ImageProps) {
|
||||
const srcImg = sharp(src)
|
||||
const extension = path.extname(src)
|
||||
const filename = path.basename(src, extension)
|
||||
const randPrefix = (Math.random() + 1).toString(36).substring(7)
|
||||
const width = 'width' in imgAttrs ? imgAttrs.width : null
|
||||
const height = 'height' in imgAttrs ? imgAttrs.height : null
|
||||
|
||||
console.log(`processing ${src} for width ${width} and height ${height}`)
|
||||
|
||||
// Make sure we can write to the dir
|
||||
await fs.mkdir(outputDir, {recursive: true})
|
||||
|
||||
// Handle where we have an array of dimensions
|
||||
if (Array.isArray(width) || Array.isArray(height)) {
|
||||
const dimension = Array.isArray(width) ? 'width' : 'height'
|
||||
const sizes = Array.isArray(width) ? width : height as number[]
|
||||
const suffix = dimension.charAt(0)
|
||||
|
||||
// Copy in original
|
||||
fs.copyFile(src, path.join(outputDir, `${randPrefix}-${filename}${extension}`))
|
||||
|
||||
// Create resized copies
|
||||
const srcSetLines = await Promise.all(sizes
|
||||
.map(async size => {
|
||||
const outputName = `${randPrefix}-${filename}-${size}${suffix}${extension}`
|
||||
const outputPath = path.join(outputDir, outputName)
|
||||
return srcImg
|
||||
.resize({[dimension]: size})
|
||||
.toFile(outputPath)
|
||||
.then(result => `${serverPath}${outputName} ${result.width}w`)
|
||||
}))
|
||||
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
srcSet={srcSetLines.join(',')}
|
||||
{...imgAttrs}
|
||||
width={undefined}
|
||||
height={undefined}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
const dimensions = {
|
||||
width: typeof width === 'number' ? width : undefined,
|
||||
height: typeof height === 'number' ? height : undefined
|
||||
}
|
||||
|
||||
const dimensionString = (() => {
|
||||
if (dimensions.height && dimensions.height)
|
||||
return `${dimensions.width}x${dimensions.height}`
|
||||
if (dimensions.width)
|
||||
return `${dimensions.width}w`
|
||||
return `${dimensions.height}h`
|
||||
})()
|
||||
|
||||
const outputName = `${randPrefix}-${filename}-${dimensionString}${extension}`
|
||||
const outputPath = path.join(outputDir, outputName)
|
||||
const outputSrc = serverPath + outputName
|
||||
await srcImg.resize(dimensions).toFile(outputPath)
|
||||
|
||||
return (
|
||||
<img
|
||||
src={outputSrc}
|
||||
{...imgAttrs}
|
||||
{...dimensions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default function ImageServerWrap(props: ImageProps) {
|
||||
return (
|
||||
<>
|
||||
{/* @ts-expect-error Async Server Component */}
|
||||
<ImageServer {...props} />
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
@ -1,4 +1,7 @@
|
||||
module.exports = {
|
||||
output: 'export',
|
||||
trailingSlash: true
|
||||
trailingSlash: true,
|
||||
images: {
|
||||
disableStaticImages: true
|
||||
},
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
import rose from '~/images/profile/rose.png'
|
||||
import dawn from '~/images/profile/dawn.png'
|
||||
import echo from '~/images/profile/echo.png'
|
||||
import corona from '~/images/profile/corona.png'
|
||||
|
||||
export default {
|
||||
rose, dawn, echo, corona
|
||||
}
|
Loading…
Reference in New Issue