diff --git a/code/frameworks/nextjs/template/stories/ServerActions.stories.tsx b/code/frameworks/nextjs/template/stories_nextjs-default-ts/ServerActions.stories.tsx
similarity index 100%
rename from code/frameworks/nextjs/template/stories/ServerActions.stories.tsx
rename to code/frameworks/nextjs/template/stories_nextjs-default-ts/ServerActions.stories.tsx
diff --git a/code/frameworks/nextjs/template/stories/server-actions.tsx b/code/frameworks/nextjs/template/stories_nextjs-default-ts/server-actions.tsx
similarity index 100%
rename from code/frameworks/nextjs/template/stories/server-actions.tsx
rename to code/frameworks/nextjs/template/stories_nextjs-default-ts/server-actions.tsx
diff --git a/code/frameworks/nextjs/template/stories_nextjs-prerelease/ServerActions.stories.tsx b/code/frameworks/nextjs/template/stories_nextjs-prerelease/ServerActions.stories.tsx
new file mode 100644
index 000000000000..f1a9ad762eed
--- /dev/null
+++ b/code/frameworks/nextjs/template/stories_nextjs-prerelease/ServerActions.stories.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+
+import { revalidatePath } from '@storybook/nextjs/cache.mock';
+import { cookies } from '@storybook/nextjs/headers.mock';
+import { getRouter, redirect } from '@storybook/nextjs/navigation.mock';
+import type { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, waitFor, within } from '@storybook/test';
+
+import { accessRoute, login, logout } from './server-actions';
+
+function Component() {
+ return (
+
+
+
+
+
+ );
+}
+
+export default {
+ component: Component,
+ tags: ['!test'],
+ parameters: {
+ nextjs: {
+ appDirectory: true,
+ navigation: {
+ pathname: '/',
+ },
+ },
+ test: {
+ // This is needed until Next will update to the React 19 beta: https://github.com/vercel/next.js/pull/65058
+ // In the React 19 beta ErrorBoundary errors (such as redirect) are only logged, and not thrown.
+ // We will also suspress console.error logs for re the console.error logs for redirect in the next framework.
+ // Using the onCaughtError react root option:
+ // react: {
+ // rootOptions: {
+ // onCaughtError(error: unknown) {
+ // if (isNextRouterError(error)) return;
+ // console.error(error);
+ // },
+ // },
+ // See: code/frameworks/nextjs/src/preview.tsx
+ dangerouslyIgnoreUnhandledErrors: true,
+ },
+ },
+} as Meta;
+
+type Story = StoryObj;
+
+export const ProtectedWhileLoggedOut: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ await userEvent.click(canvas.getByText('Access protected route'));
+
+ await expect(cookies().get).toHaveBeenCalledWith('user');
+ await expect(redirect).toHaveBeenCalledWith('/');
+
+ await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
+ },
+};
+
+export const ProtectedWhileLoggedIn: Story = {
+ beforeEach() {
+ cookies().set('user', 'storybookjs');
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ await userEvent.click(canvas.getByText('Access protected route'));
+
+ await expect(cookies().get).toHaveBeenLastCalledWith('user');
+ await expect(revalidatePath).toHaveBeenLastCalledWith('/');
+ await expect(redirect).toHaveBeenLastCalledWith('/protected');
+
+ await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
+ },
+};
+
+export const Logout: Story = {
+ beforeEach() {
+ cookies().set('user', 'storybookjs');
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ await userEvent.click(canvas.getByText('Logout'));
+ await expect(cookies().delete).toHaveBeenCalled();
+ await expect(revalidatePath).toHaveBeenCalledWith('/');
+ await expect(redirect).toHaveBeenCalledWith('/');
+
+ await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
+ },
+};
+
+export const Login: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ await userEvent.click(canvas.getByText('Login'));
+
+ await expect(cookies().set).toHaveBeenCalledWith('user', 'storybookjs');
+ await expect(revalidatePath).toHaveBeenCalledWith('/');
+ await expect(redirect).toHaveBeenCalledWith('/');
+
+ await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
+ },
+};
diff --git a/code/frameworks/nextjs/template/stories_nextjs-prerelease/server-actions.tsx b/code/frameworks/nextjs/template/stories_nextjs-prerelease/server-actions.tsx
new file mode 100644
index 000000000000..6244f78d2472
--- /dev/null
+++ b/code/frameworks/nextjs/template/stories_nextjs-prerelease/server-actions.tsx
@@ -0,0 +1,28 @@
+'use server';
+
+import { revalidatePath } from 'next/cache';
+import { cookies } from 'next/headers';
+import { redirect } from 'next/navigation';
+
+export async function accessRoute() {
+ const user = (await cookies()).get('user');
+
+ if (!user) {
+ redirect('/');
+ }
+
+ revalidatePath('/');
+ redirect(`/protected`);
+}
+
+export async function logout() {
+ (await cookies()).delete('user');
+ revalidatePath('/');
+ redirect('/');
+}
+
+export async function login() {
+ (await cookies()).set('user', 'storybookjs');
+ revalidatePath('/');
+ redirect('/');
+}