diff --git a/fixtures/view-transition/README.md b/fixtures/view-transition/README.md
new file mode 100644
index 0000000000000..7f5642ee9c4c3
--- /dev/null
+++ b/fixtures/view-transition/README.md
@@ -0,0 +1,30 @@
+# View Transition
+
+A test case for View Transitions.
+
+## Setup
+
+To reference a local build of React, first run `npm run build` at the root
+of the React project. Then:
+
+```
+cd fixtures/view-transition
+yarn
+yarn start
+```
+
+The `start` command runs a webpack dev server and a server-side rendering server in development mode with hot reloading.
+
+**Note: whenever you make changes to React and rebuild it, you need to re-run `yarn` in this folder:**
+
+```
+yarn
+```
+
+If you want to try the production mode instead run:
+
+```
+yarn start:prod
+```
+
+This will pre-build all static resources and then start a server-side rendering HTTP server that hosts the React app and service the static resources (without hot reloading).
diff --git a/fixtures/view-transition/package.json b/fixtures/view-transition/package.json
new file mode 100644
index 0000000000000..c6226c6f78958
--- /dev/null
+++ b/fixtures/view-transition/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "react-fixtures-view-transition",
+ "version": "0.1.0",
+ "private": true,
+ "devDependencies": {
+ "concurrently": "3.1.0",
+ "http-proxy-middleware": "3.0.3",
+ "react-scripts": "5.0.1",
+ "@babel/plugin-proposal-private-property-in-object": "7.21.11"
+ },
+ "dependencies": {
+ "@babel/register": "^7.25.9",
+ "express": "^4.14.0",
+ "ignore-styles": "^5.0.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "scripts": {
+ "predev": "cp -r ../../build/oss-experimental/* ./node_modules/",
+ "prestart": "cp -r ../../build/oss-experimental/* ./node_modules/",
+ "prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
+ "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
+ "dev:client": "PORT=3001 react-scripts start",
+ "dev:server": "NODE_ENV=development node server",
+ "start": "react-scripts build && NODE_ENV=production node server",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/fixtures/view-transition/public/favicon.ico b/fixtures/view-transition/public/favicon.ico
new file mode 100644
index 0000000000000..5c125de5d897c
Binary files /dev/null and b/fixtures/view-transition/public/favicon.ico differ
diff --git a/fixtures/view-transition/public/index.html b/fixtures/view-transition/public/index.html
new file mode 100644
index 0000000000000..a94d9ac64a92b
--- /dev/null
+++ b/fixtures/view-transition/public/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/fixtures/view-transition/server/index.js b/fixtures/view-transition/server/index.js
new file mode 100644
index 0000000000000..3f542b8f6e67d
--- /dev/null
+++ b/fixtures/view-transition/server/index.js
@@ -0,0 +1,71 @@
+require('ignore-styles');
+const babelRegister = require('@babel/register');
+const proxy = require('http-proxy-middleware');
+
+babelRegister({
+ ignore: [/\/(build|node_modules)\//],
+ presets: ['react-app'],
+});
+
+const express = require('express');
+const path = require('path');
+
+const app = express();
+
+// Application
+if (process.env.NODE_ENV === 'development') {
+ app.get('/', function (req, res) {
+ // In development mode we clear the module cache between each request to
+ // get automatic hot reloading.
+ for (var key in require.cache) {
+ delete require.cache[key];
+ }
+ const render = require('./render').default;
+ render(req.url, res);
+ });
+} else {
+ const render = require('./render').default;
+ app.get('/', function (req, res) {
+ render(req.url, res);
+ });
+}
+
+// Static resources
+app.use(express.static(path.resolve(__dirname, '..', 'build')));
+
+// Proxy everything else to create-react-app's webpack development server
+if (process.env.NODE_ENV === 'development') {
+ app.use(
+ '/',
+ proxy.createProxyMiddleware({
+ ws: true,
+ changeOrigin: true,
+ target: 'http://127.0.0.1:3001',
+ })
+ );
+}
+
+app.listen(3000, () => {
+ console.log('Listening on port 3000...');
+});
+
+app.on('error', function (error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
+
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+});
diff --git a/fixtures/view-transition/server/render.js b/fixtures/view-transition/server/render.js
new file mode 100644
index 0000000000000..0d956fd66caf7
--- /dev/null
+++ b/fixtures/view-transition/server/render.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import {renderToPipeableStream} from 'react-dom/server';
+
+import App from '../src/components/App';
+
+let assets;
+if (process.env.NODE_ENV === 'development') {
+ // Use the bundle from create-react-app's server in development mode.
+ assets = {
+ 'main.js': '/static/js/bundle.js',
+ // 'main.css': '',
+ };
+} else {
+ assets = require('../build/asset-manifest.json').files;
+}
+
+export default function render(url, res) {
+ res.socket.on('error', error => {
+ // Log fatal errors
+ console.error('Fatal', error);
+ });
+ let didError = false;
+ const {pipe, abort} = renderToPipeableStream(, {
+ bootstrapScripts: [assets['main.js']],
+ onShellReady() {
+ // If something errored before we started streaming, we set the error code appropriately.
+ res.statusCode = didError ? 500 : 200;
+ res.setHeader('Content-type', 'text/html');
+ pipe(res);
+ },
+ onShellError(x) {
+ // Something errored before we could complete the shell so we emit an alternative shell.
+ res.statusCode = 500;
+ res.send('Error
');
+ },
+ onError(x) {
+ didError = true;
+ console.error(x);
+ },
+ });
+ // Abandon and switch to client rendering after 5 seconds.
+ // Try lowering this to see the client recover.
+ setTimeout(abort, 5000);
+}
diff --git a/fixtures/view-transition/src/components/App.js b/fixtures/view-transition/src/components/App.js
new file mode 100644
index 0000000000000..6867b29d4c5a5
--- /dev/null
+++ b/fixtures/view-transition/src/components/App.js
@@ -0,0 +1,12 @@
+import React from 'react';
+
+import Chrome from './Chrome';
+import Page from './Page';
+
+export default function App({assets}) {
+ return (
+
+
+
+ );
+}
diff --git a/fixtures/view-transition/src/components/Chrome.css b/fixtures/view-transition/src/components/Chrome.css
new file mode 100644
index 0000000000000..b019b57b1db81
--- /dev/null
+++ b/fixtures/view-transition/src/components/Chrome.css
@@ -0,0 +1,5 @@
+body {
+ margin: 10px;
+ padding: 0;
+ font-family: sans-serif;
+}
diff --git a/fixtures/view-transition/src/components/Chrome.js b/fixtures/view-transition/src/components/Chrome.js
new file mode 100644
index 0000000000000..0cae4a8dd1953
--- /dev/null
+++ b/fixtures/view-transition/src/components/Chrome.js
@@ -0,0 +1,33 @@
+import React, {Component} from 'react';
+
+import './Chrome.css';
+
+export default class Chrome extends Component {
+ render() {
+ const assets = this.props.assets;
+ return (
+
+
+
+
+
+
+ {this.props.title}
+
+
+