Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ src/gatsby-types.d.ts
.idea/*
**/*.swp
.claude
.env
132 changes: 132 additions & 0 deletions POC_RESULTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# POC T009: Sandpack SSR Compatibility Results

## Verdict: VALIDATED

Sandpack works with Next.js 15 App Router when using the `"use client"` directive. No SSR errors, no hydration warnings, and all interactive features function correctly.

## Summary

| Aspect | Result |
|--------|--------|
| SSR Build | Passes without errors |
| Hydration | No warnings in browser console |
| Code Editor | Renders and is editable |
| Preview Panel | Shows live output |
| Refresh Button | Works correctly |
| Bundle Size | 216 kB for Sandpack page |

## Test Environment

- **Next.js Version**: 15.5.9
- **Sandpack Version**: @codesandbox/sandpack-react ^2.20.0
- **React Version**: 18.3.1
- **Node.js**: 24.11.0

## Approach Used

Sandpack components were wrapped with `"use client"` directive at the page level:

```tsx
"use client";

import {
SandpackProvider,
SandpackLayout,
SandpackCodeEditor,
SandpackPreview,
} from "@codesandbox/sandpack-react";
```

This approach:
- Compiles successfully during `next build`
- Renders correct HTML during SSR (code is visible in initial HTML)
- Hydrates without warnings
- All interactive features work after hydration

## Build Output

```
Route (app) Size First Load JS
├ ○ /sandpack-test 216 kB 318 kB

○ (Static) prerendered as static content
```

Key observations:
- Build completes with `Compiled successfully`
- Page is prerendered as static content
- Bundle includes Sandpack at 216 kB
- No SSR-related errors during build

## Browser Console

**Hydration Warnings**: None detected

The browser console was monitored during page load and hydration. No React hydration mismatch warnings were observed.

## Interactive Features Verified

1. **Code Editor**:
- Renders with syntax highlighting
- Line numbers visible
- Tab shows current file (index.js)
- Clickable and focusable

2. **Preview Panel**:
- Renders iframe with live preview
- Shows Counter app with +1/-1 buttons
- Updates when code changes

3. **Refresh Button**:
- Visible in preview panel
- Clickable and refreshes the preview

## Screenshot

![Sandpack Working](poc-nextjs/sandpack-working.png)

## Workarounds Required

**None required for basic usage.**

The only requirement is using `"use client"` directive, which is standard practice for interactive components in Next.js App Router.

### Note on ESLint

There was an ESLint configuration conflict with the parent Docs repo that caused a warning during build:
```
ESLint: Converting circular structure to JSON
```

This is not related to Sandpack - it's due to conflicting ESLint configs between the poc-nextjs folder and the parent Docs repo. In a real migration, this would be resolved by proper ESLint configuration.

## Comparison with Known Issues

The [GitHub issue #1093](https://github.com/codesandbox/sandpack/issues/1093) mentioned SSR problems with App Router. Based on this POC:

- **Issue Status**: Appears to be resolved in current Sandpack version (2.20.0)
- **No `dynamic(() => import(...), { ssr: false })` needed**
- Standard `"use client"` directive is sufficient

## Recommendations

1. **Proceed with Next.js migration** - Sandpack is compatible
2. **Use `"use client"` directive** on pages/components containing Sandpack
3. **Monitor bundle size** - Sandpack adds ~216KB to page bundle
4. **Test with actual Docs examples** - This POC used a simple counter; test with Ably SDK examples

## Files Created

```
poc-nextjs/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ └── sandpack-test/
│ └── page.tsx # Main test page with Sandpack
├── package.json
├── tsconfig.json
├── next.config.js
├── build_output.txt
└── sandpack-working.png # Screenshot of working Sandpack
```
4 changes: 4 additions & 0 deletions poc-nextjs/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"root": true,
"extends": ["next/core-web-vitals"]
}
1 change: 1 addition & 0 deletions poc-nextjs/.next/BUILD_ID
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
g66j69sSQsBnb8EmFomOc
35 changes: 35 additions & 0 deletions poc-nextjs/.next/app-build-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"pages": {
"/_not-found/page": [
"static/chunks/webpack-407051e26481fe3b.js",
"static/chunks/4bd1b696-8b20f5740c9f0351.js",
"static/chunks/255-7ceb435cc98b8fa0.js",
"static/chunks/main-app-bb7b2ddd9864def4.js",
"static/chunks/app/_not-found/page-ea79e66c8eb3a576.js"
],
"/layout": [
"static/chunks/webpack-407051e26481fe3b.js",
"static/chunks/4bd1b696-8b20f5740c9f0351.js",
"static/chunks/255-7ceb435cc98b8fa0.js",
"static/chunks/main-app-bb7b2ddd9864def4.js",
"static/chunks/app/layout-e387bf83a41924f7.js"
],
"/sandpack-test/page": [
"static/chunks/webpack-407051e26481fe3b.js",
"static/chunks/4bd1b696-8b20f5740c9f0351.js",
"static/chunks/255-7ceb435cc98b8fa0.js",
"static/chunks/main-app-bb7b2ddd9864def4.js",
"static/chunks/e58a7f8f-aa5a5c7440a379f5.js",
"static/chunks/363642f4-976a6f53af18620e.js",
"static/chunks/665-e0c7cda40525d06c.js",
"static/chunks/app/sandpack-test/page-a2195aad9a6fbf34.js"
],
"/page": [
"static/chunks/webpack-407051e26481fe3b.js",
"static/chunks/4bd1b696-8b20f5740c9f0351.js",
"static/chunks/255-7ceb435cc98b8fa0.js",
"static/chunks/main-app-bb7b2ddd9864def4.js",
"static/chunks/app/page-8eadad33fc0dcada.js"
]
}
}
5 changes: 5 additions & 0 deletions poc-nextjs/.next/app-path-routes-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"/_not-found/page": "/_not-found",
"/sandpack-test/page": "/sandpack-test",
"/page": "/"
}
33 changes: 33 additions & 0 deletions poc-nextjs/.next/build-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"polyfillFiles": [
"static/chunks/polyfills-42372ed130431b0a.js"
],
"devFiles": [],
"ampDevFiles": [],
"lowPriorityFiles": [
"static/g66j69sSQsBnb8EmFomOc/_buildManifest.js",
"static/g66j69sSQsBnb8EmFomOc/_ssgManifest.js"
],
"rootMainFiles": [
"static/chunks/webpack-407051e26481fe3b.js",
"static/chunks/4bd1b696-8b20f5740c9f0351.js",
"static/chunks/255-7ceb435cc98b8fa0.js",
"static/chunks/main-app-bb7b2ddd9864def4.js"
],
"rootMainFilesTree": {},
"pages": {
"/_app": [
"static/chunks/webpack-407051e26481fe3b.js",
"static/chunks/framework-a6e0b7e30f98059a.js",
"static/chunks/main-8402b7bf58a0fc75.js",
"static/chunks/pages/_app-7d307437aca18ad4.js"
],
"/_error": [
"static/chunks/webpack-407051e26481fe3b.js",
"static/chunks/framework-a6e0b7e30f98059a.js",
"static/chunks/main-8402b7bf58a0fc75.js",
"static/chunks/pages/_error-cb2a52f75f2162e2.js"
]
},
"ampFirstPages": []
}
1 change: 1 addition & 0 deletions poc-nextjs/.next/cache/.previewinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"previewModeId":"6069f63450a6ec29259d3b6d669e9b26","previewModeSigningKey":"0cc9d93a0b81148759d685ceac5b77cc60fa387c47e0b73ed926458d83a9460a","previewModeEncryptionKey":"f971f1f210812c4a8f6c9fca3c1e3add0db4b814810ddb167a156959c4cc0d8a","expireAt":1769696621954}
1 change: 1 addition & 0 deletions poc-nextjs/.next/cache/.rscinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"encryption.key":"0Rw00CjQGWnzphrXHn5hTuLOrpc6AXnEusB2yPbvfMw=","encryption.expire_at":1769696621922}
1 change: 1 addition & 0 deletions poc-nextjs/.next/cache/.tsbuildinfo

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions poc-nextjs/.next/cache/eslint/.cache_sqcocs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"/Users/matthew.oriordan/Projects/Ably/docs-worktrees/poc-09-sandpack-ssr/poc-nextjs/app/layout.tsx":"1","/Users/matthew.oriordan/Projects/Ably/docs-worktrees/poc-09-sandpack-ssr/poc-nextjs/app/page.tsx":"2","/Users/matthew.oriordan/Projects/Ably/docs-worktrees/poc-09-sandpack-ssr/poc-nextjs/app/sandpack-test/page.tsx":"3"},{"size":339,"mtime":1768486980129,"results":"4","hashOfConfig":"5"},{"size":415,"mtime":1768486985128,"results":"6","hashOfConfig":"5"},{"size":2184,"mtime":1768486997379,"results":"7","hashOfConfig":"5"},{"filePath":"8","messages":"9","suppressedMessages":"10","errorCount":4,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":4,"fixableWarningCount":0,"source":null},"caaode",{"filePath":"11","messages":"12","suppressedMessages":"13","errorCount":3,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":3,"fixableWarningCount":0,"source":null},{"filePath":"14","messages":"15","suppressedMessages":"16","errorCount":10,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":10,"fixableWarningCount":0,"source":null},"/Users/matthew.oriordan/Projects/Ably/docs-worktrees/poc-09-sandpack-ssr/poc-nextjs/app/layout.tsx",["17","18","19","20"],[],"/Users/matthew.oriordan/Projects/Ably/docs-worktrees/poc-09-sandpack-ssr/poc-nextjs/app/page.tsx",["21","22","23"],[],"/Users/matthew.oriordan/Projects/Ably/docs-worktrees/poc-09-sandpack-ssr/poc-nextjs/app/sandpack-test/page.tsx",["24","25","26","27","28","29","30","31","32","33"],[],{"ruleId":"34","severity":2,"message":"35","line":1,"column":31,"nodeType":null,"messageId":"36","endLine":1,"endColumn":37,"fix":"37"},{"ruleId":"34","severity":2,"message":"38","line":4,"column":10,"nodeType":null,"messageId":"36","endLine":4,"endColumn":28,"fix":"39"},{"ruleId":"34","severity":2,"message":"40","line":5,"column":16,"nodeType":null,"messageId":"36","endLine":5,"endColumn":61,"fix":"41"},{"ruleId":"34","severity":2,"message":"42","line":8,"column":37,"nodeType":null,"messageId":"36","endLine":12,"endColumn":1,"fix":"43"},{"ruleId":"34","severity":2,"message":"44","line":1,"column":18,"nodeType":null,"messageId":"36","endLine":1,"endColumn":29,"fix":"45"},{"ruleId":"34","severity":2,"message":"46","line":5,"column":29,"nodeType":null,"messageId":"36","endLine":5,"endColumn":35,"fix":"47"},{"ruleId":"34","severity":2,"message":"48","line":8,"column":32,"nodeType":null,"messageId":"36","endLine":8,"endColumn":38,"fix":"49"},{"ruleId":"34","severity":2,"message":"50","line":1,"column":1,"nodeType":null,"messageId":"36","endLine":1,"endColumn":13,"fix":"51"},{"ruleId":"34","severity":2,"message":"52","line":3,"column":9,"nodeType":null,"messageId":"36","endLine":8,"endColumn":37,"fix":"53"},{"ruleId":"34","severity":2,"message":"54","line":9,"column":29,"nodeType":null,"messageId":"36","endLine":9,"endColumn":59,"fix":"55"},{"ruleId":"34","severity":2,"message":"56","line":32,"column":29,"nodeType":null,"messageId":"36","endLine":32,"endColumn":73,"fix":"57"},{"ruleId":"34","severity":2,"message":"46","line":36,"column":32,"nodeType":null,"messageId":"36","endLine":36,"endColumn":38,"fix":"58"},{"ruleId":"34","severity":2,"message":"59","line":41,"column":13,"nodeType":null,"messageId":"36","endLine":41,"endColumn":24,"fix":"60"},{"ruleId":"34","severity":2,"message":"61","line":45,"column":21,"nodeType":null,"messageId":"36","endLine":45,"endColumn":29,"fix":"62"},{"ruleId":"34","severity":2,"message":"63","line":54,"column":32,"nodeType":null,"messageId":"36","endLine":58,"endColumn":12,"fix":"64"},{"ruleId":"34","severity":2,"message":"65","line":59,"column":29,"nodeType":null,"messageId":"36","endLine":63,"endColumn":12,"fix":"66"},{"ruleId":"34","severity":2,"message":"67","line":68,"column":32,"nodeType":null,"messageId":"36","endLine":68,"endColumn":99,"fix":"68"},"prettier/prettier","Replace `\"next\"` with `'next'`","replace",{"range":"69","text":"70"},"Replace `\"Sandpack·SSR·POC\"` with `'Sandpack·SSR·POC'`",{"range":"71","text":"72"},"Replace `\"Testing·Sandpack·with·Next.js·15·App·Router\"` with `'Testing·Sandpack·with·Next.js·15·App·Router'`",{"range":"73","text":"74"},"Replace `⏎··children,⏎}:·{⏎··children:·React.ReactNode;⏎` with `·children·}:·{·children:·React.ReactNode·`",{"range":"75","text":"76"},"Replace `\"next/link\"` with `'next/link'`",{"range":"77","text":"78"},"Replace `\"2rem\"` with `'2rem'`",{"range":"79","text":"80"},"Replace `\"1rem\"` with `'1rem'`",{"range":"81","text":"82"},"Replace `\"use·client\"` with `'use·client'`",{"range":"83","text":"84"},"Replace `⏎··SandpackProvider,⏎··SandpackLayout,⏎··SandpackCodeEditor,⏎··SandpackPreview,⏎}·from·\"@codesandbox/sandpack-react\"` with `·SandpackProvider,·SandpackLayout,·SandpackCodeEditor,·SandpackPreview·}·from·'@codesandbox/sandpack-react'`",{"range":"85","text":"86"},"Replace `\"@codesandbox/sandpack-themes\"` with `'@codesandbox/sandpack-themes'`",{"range":"87","text":"88"},"Replace `\"2rem\",·maxWidth:·\"1200px\",·margin:·\"0·auto\"` with `'2rem',·maxWidth:·'1200px',·margin:·'0·auto'`",{"range":"89","text":"90"},{"range":"91","text":"80"},"Replace `\"/index.js\"` with `'/index.js'`",{"range":"92","text":"93"},"Replace `\"^2.0.0\"` with `'^2.0.0'`",{"range":"94","text":"95"},"Replace `⏎··············showLineNumbers⏎··············showTabs⏎··············style={{·height:·\"400px\"·}}⏎···········` with `·showLineNumbers·showTabs·style={{·height:·'400px'·}}`",{"range":"96","text":"97"},"Replace `⏎··············showRefreshButton⏎··············showOpenInCodeSandbox={false}⏎··············style={{·height:·\"400px\"·}}⏎···········` with `·showRefreshButton·showOpenInCodeSandbox={false}·style={{·height:·'400px'·}}`",{"range":"98","text":"99"},"Replace `\"2rem\",·padding:·\"1rem\",·background:·\"#f5f5f5\",·borderRadius:·\"8px\"` with `'2rem',·padding:·'1rem',·background:·'#f5f5f5',·borderRadius:·'8px'`",{"range":"100","text":"101"},[30,36],"'next'",[84,102],"'Sandpack SSR POC'",[119,164],"'Testing Sandpack with Next.js 15 App Router'",[206,253]," children }: { children: React.ReactNode ",[17,28],"'next/link'",[103,109],"'2rem'",[248,254],"'1rem'",[0,12],"'use client'",[23,139]," SandpackProvider, SandpackLayout, SandpackCodeEditor, SandpackPreview } from '@codesandbox/sandpack-react'",[169,199],"'@codesandbox/sandpack-themes'",[826,870],"'2rem', maxWidth: '1200px', margin: '0 auto'",[1020,1026],[1147,1158],"'/index.js'",[1258,1266],"'^2.0.0'",[1455,1562]," showLineNumbers showTabs style={{ height: '400px' }}",[1594,1724]," showRefreshButton showOpenInCodeSandbox={false} style={{ height: '400px' }}",[1829,1896],"'2rem', padding: '1rem', background: '#f5f5f5', borderRadius: '8px'"]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
6 changes: 6 additions & 0 deletions poc-nextjs/.next/diagnostics/build-diagnostics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"buildStage": "static-generation",
"buildOptions": {
"useBuildWorker": "true"
}
}
1 change: 1 addition & 0 deletions poc-nextjs/.next/diagnostics/framework.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"Next.js","version":"15.5.9"}
6 changes: 6 additions & 0 deletions poc-nextjs/.next/export-marker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": 1,
"hasExportPathMap": false,
"exportTrailingSlash": false,
"isNextImageImported": false
}
57 changes: 57 additions & 0 deletions poc-nextjs/.next/images-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"version": 1,
"images": {
"deviceSizes": [
640,
750,
828,
1080,
1200,
1920,
2048,
3840
],
"imageSizes": [
16,
32,
48,
64,
96,
128,
256,
384
],
"path": "/_next/image",
"loader": "default",
"loaderFile": "",
"domains": [],
"disableStaticImages": false,
"minimumCacheTTL": 60,
"formats": [
"image/webp"
],
"dangerouslyAllowSVG": false,
"contentSecurityPolicy": "script-src 'none'; frame-src 'none'; sandbox;",
"contentDispositionType": "attachment",
"remotePatterns": [],
"unoptimized": false,
"sizes": [
640,
750,
828,
1080,
1200,
1920,
2048,
3840,
16,
32,
48,
64,
96,
128,
256,
384
]
}
}
Loading