A portfolio starter using:
next.js
tailwindcss
storybook
netlify cms
:warning: READ THIS :warning: This starter is currently a WIP. There's a bunch of stuff getting added at the moment. You're free to take it and use it if you like as-is. But, there will be some large changes with regards to how things work. Also, there are a lot of features on the way which are useful:
This starter is intended for exported static sites. Think of it like an opinionated hybrid of 11ty
with React
. It's intended to be used with Netlify
. That gives us the opportunity to use all of the Netlify "perks" such as CMS
, forms
, functions
, analytics
, etc. with ease. All that whilst still being able to
use the DX of Next.js.
Relies on netlify-cms-proxy-server
and using local_backend
is a beta feature discussed here.
Image
with sharp
optimisationGoing with this option means that you use images normally as you would by putting them inside /public
. However, you use the Image
component located at @/components/image
.
<Image
className="w-24 h-24"
src="/images/awesome.jpg"
alt="An awesome picture"
width={250}
height={250}
sizes={['(max-width: 400px) 100px', '250px']}
/>
It's like a normal img
but you can pass it sizes
. And these sizes get applied to source
elements within a picture
element. At build time, any images that need to be, get optimised. Under the hood, sharp
processes images in the public
folder that use this component. The output directory defaults to enhanced/images
.
For example, at build time, the component above renders.
<picture data-sizes="(max-width: 400px) 100px,250px" class="block w-24 h-24">
<source
type="image/avif"
srcset="/enhanced/images/awesome_100.avif 100w,/enhanced/images/awesome_250.avif 250w"
sizes="(max-width: 400px) 100px,250px">
<source
type="image/webp"
srcset="/enhanced/images/awesome_100.webp 100w,/enhanced/images/awesome_250.webp 250w"
sizes="(max-width: 400px) 100px,250px">
<source
type="image/png"
srcset="/enhanced/images/awesome_100.png 100w,/enhanced/images/awesome_250.png 250w"
sizes="(max-width: 400px) 100px,250px">
<img
src="/images/awesome.jpg"
alt="An awesome picture"
class="h-full w-full object-cover"
loading="lazy"
decoding="async"
width="250"
height="250">
</picture>
Need to pass styles down to that nested image? Use the imgProps
prop to pass things down.
<Image
className="w-24 h-24"
imgProps={{
className: "h-full w-full object-cover",
}}
src="/images/awesome.jpg"
alt="An awesome picture"
width={250}
height={250}
sizes={['(max-width: 400px) 100px', '250px']}
/>
You can optimise in various formats supported by sharp
. Export a config from n3xt.config.js
with the formats you want to use.
module.exports = {
images: {
types: ['avif', 'webp', 'png'],
},
}
NOTE:: This is all very opinionated. I'd recommend digging into the components and the image optimisation code. Tweak transforms/img-optimisation.js
to your tastes.
This method works by building the project using next build
as normal. But, it then scrapes the build output and generates optimised assets before export. Assets are only generated when needed. If they already exist, they aren't generated again unless deleted.
next/image
Now for next/image
. It works the same as usual. But, you'll hit issues if you try exporting with the default
loader. Using a CDN for images that use next/image
is the option here. But, you don't want to use up all your bandwidth whilst working on your site!
For this, use environment variables. Store the path for your CDN provider in .env.local
.
And then switch between loader based on process.env.NODE_ENV
. For example, with cloudinary
.
module.exports = {
images: {
loader: process.env.NODE_ENV !== 'production' ? 'default' : 'cloudinary',
domains: ['res.cloudinary.com', 'localhost'],
...(process.env.NODE_ENV === 'production' && {
path: process.env.CLOUDINARY_PATH,
}),
},
}
The caveat here is that you must mirror the media paths from your CDN provider to your public folder.
For example,
https://res.cloudinary.com/<username>/image/upload/images/awesome.png
Would map to.
/public/images/awesome.png