Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# Pull Request

## Summary

-
-
-


## Checklist

- [ ] **PR title follows conventional commits format** ([see format guide](../CONTRIBUTING.md#committing-changes))
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
Expand Down
9 changes: 4 additions & 5 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ jobs:
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -46,12 +46,11 @@ jobs:
- Performance considerations
- Security concerns
- Test coverage

Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.

Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.

# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'

5 changes: 2 additions & 3 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
Expand All @@ -46,5 +46,4 @@ jobs:
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'

claude_args: "--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)"
111 changes: 78 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,7 @@ Next-generation image optimization for React and Next.js applications.

## Overview

Drop-in image optimization with automatic format conversion (AVIF/WebP), lazy loading, and responsive images. Zero-config for Next.js, minimal setup for React.

### 🚧 React Server Components (RSC) Status

#### Next.js Package

- The `@snapkit-studio/nextjs` `Image` component is currently **client-only**.
- RSC 및 서버 컴포넌트 환경에서는 직접 사용할 수 없으며, `'use client'` 지시자가 필요합니다.
- 향후 RSC 지원을 준비하면서도 기존 Next.js Image 통합 흐름은 유지됩니다.

#### React Package

- **Framework-agnostic RSC support** - works in any React 18+ environment
- **ServerImage and ClientImage components** for explicit control
- **Smaller bundle size** without Next.js dependencies
- **Consistent explicit component selection** approach
Drop-in image optimization with automatic format conversion (AVIF/WebP), lazy loading, and responsive images. Supports **flexible CDN configuration** - use Snapkit CDN for zero-config optimization or integrate with your existing CDN infrastructure (CloudFront, Google Cloud Storage, Cloudflare, etc.).

## Packages

Expand All @@ -41,8 +26,13 @@ npm install @snapkit-studio/nextjs
```

```bash
# .env.local
NEXT_PUBLIC_SNAPKIT_ORGANIZATION_NAME=your-organization-name
# .env.local - Using Snapkit CDN (Default)
NEXT_PUBLIC_IMAGE_CDN_PROVIDER=snapkit
NEXT_PUBLIC_SNAPKIT_ORGANIZATION=your-organization-name

# Or using Custom CDN (CloudFront example)
# NEXT_PUBLIC_IMAGE_CDN_PROVIDER=custom
# NEXT_PUBLIC_IMAGE_CDN_URL=https://d1234567890.cloudfront.net
```

```tsx
Expand Down Expand Up @@ -75,8 +65,13 @@ npm install @snapkit-studio/react
```

```bash
# .env
VITE_SNAPKIT_ORGANIZATION_NAME=your-organization-name
# .env - Using Snapkit CDN (Default)
VITE_IMAGE_CDN_PROVIDER=snapkit
VITE_SNAPKIT_ORGANIZATION=your-organization-name

# Or using Custom CDN (Google Cloud Storage example)
# VITE_IMAGE_CDN_PROVIDER=custom
# VITE_IMAGE_CDN_URL=https://storage.googleapis.com/my-image-bucket
```

```tsx
Expand Down Expand Up @@ -122,23 +117,73 @@ function App() {
| DPR Optimization | ✅ | ✅ |
| Provider Required | ❌ | ❌ |

## Environment Variables
## CDN Configuration

### Next.js
Snapkit supports flexible CDN configuration. Choose between Snapkit's optimized CDN or integrate with your existing infrastructure:

| Variable | Default | Description |
| --------------------------------------------- | -------- | --------------------------------------------- |
| `NEXT_PUBLIC_SNAPKIT_ORGANIZATION_NAME` | Required | Your Snapkit organization name |
| `NEXT_PUBLIC_SNAPKIT_DEFAULT_QUALITY` | `85` | Default image quality (1-100) |
| `NEXT_PUBLIC_SNAPKIT_DEFAULT_OPTIMIZE_FORMAT` | `auto` | Default format: `auto`, `avif`, `webp`, `off` |
### Snapkit CDN (Recommended)

### React (Vite/CRA)
Zero-configuration setup with automatic optimization, smart format delivery, and global edge caching:

```bash
# Next.js
NEXT_PUBLIC_IMAGE_CDN_PROVIDER=snapkit
NEXT_PUBLIC_SNAPKIT_ORGANIZATION=your-organization

# React/Vite
VITE_IMAGE_CDN_PROVIDER=snapkit
VITE_SNAPKIT_ORGANIZATION=your-organization
```

### Custom CDN Integration

Use your existing CDN infrastructure with Snapkit's optimization features:

```bash
# AWS CloudFront
NEXT_PUBLIC_IMAGE_CDN_PROVIDER=custom
NEXT_PUBLIC_IMAGE_CDN_URL=https://d1234567890.cloudfront.net

# Google Cloud Storage
VITE_IMAGE_CDN_PROVIDER=custom
VITE_IMAGE_CDN_URL=https://storage.googleapis.com/my-image-bucket

# Cloudflare or any custom domain
IMAGE_CDN_PROVIDER=custom
IMAGE_CDN_URL=https://images.example.com
```

### Environment Variables Reference

#### Next.js

| Variable | Required For | Description |
| --------------------------------------- | ------------ | ----------------------------------- |
| `NEXT_PUBLIC_IMAGE_CDN_PROVIDER` | All setups | CDN provider: `snapkit` or `custom` |
| `NEXT_PUBLIC_SNAPKIT_ORGANIZATION` | Snapkit CDN | Your Snapkit organization name |
| `NEXT_PUBLIC_IMAGE_CDN_URL` | Custom CDN | Your custom CDN base URL |

#### React (Vite/CRA)

| Variable | Required For | Description |
| ------------------------------ | ------------ | ----------------------------------- |
| `VITE_IMAGE_CDN_PROVIDER` | All setups | CDN provider: `snapkit` or `custom` |
| `VITE_SNAPKIT_ORGANIZATION` | Snapkit CDN | Your Snapkit organization name |
| `VITE_IMAGE_CDN_URL` | Custom CDN | Your custom CDN base URL |

#### Node.js/Server

| Variable | Required For | Description |
| ------------------------ | ------------ | ----------------------------------- |
| `IMAGE_CDN_PROVIDER` | All setups | CDN provider: `snapkit` or `custom` |
| `SNAPKIT_ORGANIZATION` | Snapkit CDN | Your Snapkit organization name |
| `IMAGE_CDN_URL` | Custom CDN | Your custom CDN base URL |

## Migration Guide

Migrating from existing image solutions? Check out our comprehensive migration guides:

| Variable | Default | Description |
| -------------------------------------- | -------- | --------------------------------------------- |
| `VITE_SNAPKIT_ORGANIZATION_NAME` | Required | Your Snapkit organization name |
| `VITE_SNAPKIT_DEFAULT_QUALITY` | `85` | Default image quality (1-100) |
| `VITE_SNAPKIT_DEFAULT_OPTIMIZE_FORMAT` | `auto` | Default format: `auto`, `avif`, `webp`, `off` |
- **[From Next.js Image →](./docs/MIGRATION-FROM-NEXTJS.md)** Complete step-by-step guide with code examples and troubleshooting

## Live Demos

Expand Down
25 changes: 22 additions & 3 deletions apps/nextjs-demo/.env.local
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
NEXT_PUBLIC_SNAPKIT_ORGANIZATION_NAME=snapkit
NEXT_PUBLIC_SNAPKIT_DEFAULT_QUALITY=85
NEXT_PUBLIC_SNAPKIT_DEFAULT_OPTIMIZE_FORMAT=webp
# CDN Provider Configuration
# Uncomment the provider you want to use:

# Option 1: Snapkit CDN (Default)
NEXT_PUBLIC_IMAGE_CDN_PROVIDER=snapkit
NEXT_PUBLIC_SNAPKIT_ORGANIZATION=snapkit

# Option 2: Custom CDN (CloudFront example)
# NEXT_PUBLIC_IMAGE_CDN_PROVIDER=custom
# NEXT_PUBLIC_IMAGE_CDN_URL=https://d1234567890.cloudfront.net

# Option 3: Custom CDN (Google Cloud Storage example)
# NEXT_PUBLIC_IMAGE_CDN_PROVIDER=custom
# NEXT_PUBLIC_IMAGE_CDN_URL=https://storage.googleapis.com/my-image-bucket

# Option 4: Custom CDN (Cloudflare example)
# NEXT_PUBLIC_IMAGE_CDN_PROVIDER=custom
# NEXT_PUBLIC_IMAGE_CDN_URL=https://images.example.com

# Image optimization settings
NEXT_PUBLIC_IMAGE_DEFAULT_QUALITY=85
NEXT_PUBLIC_IMAGE_DEFAULT_FORMAT=webp
26 changes: 13 additions & 13 deletions apps/nextjs-demo/app/examples/AdvancedTransformsExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export function AdvancedTransformsExample() {
alt={`Quality ${quality}%`}
width={200}
height={150}
quality={quality}
className="w-full rounded border object-cover"
transforms={{ quality }}
/>
<p className="mt-1 text-xs text-gray-500">
{quality >= 85
Expand All @@ -57,10 +57,10 @@ export function AdvancedTransformsExample() {

<CodeBlock language="tsx">
{`// Optimizing for different quality tiers
<Image transforms={{ quality: 100 }} /> // Highest quality
<Image transforms={{ quality: 85 }} /> // Web standard
<Image transforms={{ quality: 70 }} /> // Mobile standard
<Image transforms={{ quality: 50 }} /> // Low-bandwidth networks`}
<Image quality={100} /> // Highest quality
<Image quality={85} /> // Web standard
<Image quality={70} /> // Mobile standard
<Image quality={50} /> // Low-bandwidth networks`}
</CodeBlock>

{/* Dynamic resizing */}
Expand All @@ -87,7 +87,6 @@ export function AdvancedTransformsExample() {
width={size.width}
height={size.height}
className="w-full rounded border object-cover"
transforms={{ width: size.width, height: size.height }}
/>
</div>
))}
Expand All @@ -96,10 +95,10 @@ export function AdvancedTransformsExample() {

<CodeBlock language="tsx">
{`// Dynamic resizing
<Image transforms={{ width: 800, height: 600 }} /> // High resolution
<Image transforms={{ width: 400, height: 300 }} /> // Standard
<Image transforms={{ width: 200, height: 150 }} /> // Mobile
<Image transforms={{ width: 100, height: 100 }} /> // Thumbnail`}
<Image width={800} height={600} /> // High resolution
<Image width={400} height={300} /> // Standard
<Image width={200} height={150} /> // Mobile
<Image width={100} height={100} /> // Thumbnail`}
</CodeBlock>

{/* Composite filter combinations */}
Expand All @@ -118,10 +117,10 @@ export function AdvancedTransformsExample() {
width={250}
height={188}
className="w-full rounded border object-cover"
quality={70}
transforms={{
grayscale: true,
blur: 1,
quality: 70,
}}
/>
<p className="mt-1 text-xs text-gray-500">
Expand All @@ -138,9 +137,9 @@ export function AdvancedTransformsExample() {
width={250}
height={188}
className="w-full rounded border object-cover"
quality={90}
transforms={{
blur: 3,
quality: 90,
}}
/>
<p className="mt-1 text-xs text-gray-500">
Expand Down Expand Up @@ -375,6 +374,7 @@ const [transforms, setTransforms] = useState({
alt={`${style.name} style`}
width={150}
height={113}
quality={style.transforms.quality}
className="w-full rounded object-cover"
transforms={style.transforms}
/>
Expand All @@ -401,7 +401,7 @@ const artStyles = [
{artStyles.map((style, index) => (
<Image
key={index}
src="/fox.jpg"
src="/landing-page/fox.jpg"
alt={\`\${style.name} style\`}
transforms={style.transforms}
/>
Expand Down
6 changes: 3 additions & 3 deletions apps/nextjs-demo/app/examples/BasicImageExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ function BasicExample() {
<CodeBlock language="tsx">
{`// Images in different sizes
<div className="flex gap-4">
<Image src="/fox.jpg" alt="Small image" width={120} height={90} />
<Image src="/fox.jpg" alt="Medium image" width={200} height={150} />
<Image src="/fox.jpg" alt="Large image" width={300} height={225} />
<Image src="/landing-page/fox.jpg" alt="Small image" width={120} height={90} />
<Image src="/landing-page/fox.jpg" alt="Medium image" width={200} height={150} />
<Image src="/landing-page/fox.jpg" alt="Large image" width={300} height={225} />
</div>`}
</CodeBlock>
</div>
Expand Down
Loading
Loading