Migrating from Next.js to Catalyst
Step 1: Project Setup
Create a new Catalyst project:
npx create-catalyst-app@latest
Perform the entire migration in a new folder to avoid dependency conflicts between Next.js and Catalyst.
Step 2: Project Structure
-
Understand the differences in project structure:
Next.js:
pages/public/styles/Catalyst:
config/ # Configuration keyssrc/ # Application source codecomponents/routes/services/utils/client/ # Client app entry pointserver/ # API and middlewarebuild/ # Bundled codeFor detailed information about Catalyst's folder structure, see: Folder Structure Guide
-
Move your existing components from various locations to
src/components/. -
Relocate any utility functions to
src/utils/.
Step 3: Routing
-
Replace Next.js file-based routing with Catalyst's route configuration.
-
Open src/routes/index.ts.
-
Define your routes using the
react-router-v6style:const routes = [{path: "/",index: true,component: HomePage,},{path: "/cart",component: CartComponent,children: [{path: "",component: CartChild},{path: "prescription",component: Prescription}],},{path: "/health-products",component: HealthProducts,},] -
Update dynamic routes to use react-router-v6 syntax:
[id].js→path: "/products/:id"[...slug].js→path: "/products/*"
Step 4: Migration of Folder Structure
-
Move your page components from
pages/to act as route components in the routes definition. -
Ensure all other components are in
src/components/. -
Move your API routes from
pages/api/to theserver/directory in Catalyst.
Step 5: Next.js App Router to Catalyst Migration
If you're migrating from Next.js App Router (with React Server Components), here's how to adapt your components:
Key Differences:
Next.js RSC:
- Components can be async and fetch data directly
- Server Components render on the server only
- Client Components require 'use client' directive
Catalyst Framework:
- Traditional React components with separate data fetchers
serverFetcher: Runs during SSR, server-only codeclientFetcher: Runs during client-side navigation- Data accessed via
useCurrentRouteDatahook
Migration Example: Basic Data Fetching
Next.js RSC Implementation:
// app/products/page.tsxasync function ProductsPage() {const products = await fetch('https://api.example.com/products').then(r => r.json());return (<div><h1>Products</h1>{products.map(product => (<div key={product.id}>{product.name}</div>))}</div>);}export default ProductsPage;
Catalyst Implementation:
// pages/Products.jsximport { useCurrentRouteData } from "catalyst-core";const ProductsPage = () => {const { data: products, isFetching, error } = useCurrentRouteData();if (isFetching) return <div>Loading...</div>;if (error) return <div>Error loading products</div>;return (<div><h1>Products</h1>{products?.map(product => (<div key={product.id}>{product.name}</div>))}</div>);};// Client-side navigationProductsPage.clientFetcher = async () => {const res = await fetch('https://api.example.com/products');const products = await res.json();return products;};// Server-side renderingProductsPage.serverFetcher = async () => {const res = await fetch('https://api.example.com/products');const products = await res.json();return products;};export default ProductsPage;
Key Migration Steps:
- Convert async components to regular React components
- Move data fetching logic from component body to separate
serverFetcher/clientFetcherfunctions - Add loading and error handling in the component using
useCurrentRouteData - Remove 'use client' directives - not needed in Catalyst
Step 6: Data Fetching
Key Migration Steps:
-
Replace Next.js data fetching methods (
getServerSideProps, getStaticProps) with Catalyst's data fetching methods:getServerSideProps→ComponentName.serverFetchergetStaticProps→ComponentName.serverFetcher(with caching logic if needed)
-
Update components to use the useCurrentRouteData hook:
const { isFetching, isFetched, error, data, refetch, clear } = useCurrentRouteData()
Migration Example
Next.js Implementation:
// pages/user/[id].jsexport default function UserProfile({ user }) {return <div>{user.name}</div>}export const getServerSideProps = async (context) => {const { id } = context.paramsconst res = await fetch(`https://api.example.com/users/${id}`)const user = await res.json()return { props: { user } }}
Catalyst Implementation:
// src/components/UserProfile.jsimport { useCurrentRouteData } from 'catalyst-core'const UserProfile = () => {const { data } = useCurrentRouteData()return <div>{data?.name}</div>}UserProfile.serverFetcher = async ({ params }) => {const { id } = paramsconst res = await fetch(`https://api.example.com/users/${id}`)return await res.json()}UserProfile.clientFetcher = async ({ params }) => {const { id } = paramsconst res = await fetch(`https://api.example.com/users/${id}`)return await res.json()}export default UserProfile
Step 7: Middlewares
- In Next.js, you used middleware.ts (or .js) in the project root.
- Catalyst offers a flexible approach to defining server-side code, granting you greater control and customization over server operations.
// server/index.jsexport function addMiddlewares(app) {app.use()}
Step 8: State Management
Migration Example: Redux Store Setup
Next.js Implementation:
// store/store.jsimport { createStore } from 'redux'import rootReducer from './reducers'const store = createStore(rootReducer)export default store// pages/_app.jsimport { Provider } from 'react-redux'import store from '../store/store'export default function App({ Component, pageProps }) {return (<Provider store={store}><Component {...pageProps} /></Provider>)}
Catalyst Implementation:
When you create a new Catalyst application using create-catalyst-app, the following Redux setup comes pre-configured:
- Store configuration at
src/js/store/index.jswith proper initialization - Redux Provider wrapper automatically handled by the framework - no need to manually wrap your app
- Server-side store integration with access to the store during SSR via fetchers
- Initial state hydration from server to client automatically managed
// src/js/store/index.jsimport { createStore } from 'redux'import homeReducer from '@reducers/homeReducer.js'export default function configureStore(initialState, request) {// request object available when store is initialized on serverconst store = createStore(homeReducer, initialState)return store}
Key Migration Steps:
-
Move Redux store setup from your custom location to
src/js/store/index.js -
Remove Provider wrapper from your app component - Catalyst handles this automatically
-
Access store in fetchers using the
storeparameter:HomePage.serverFetcher = async ({ req }, { store }) => {return store.dispatch(getHomePageData())} -
If using other state management solutions: Implement at the root of your Catalyst application and update components accordingly.
Step 9: Styling
Migration Example: CSS Modules
Next.js Implementation:
// styles/Home.module.css.container { padding: 2rem; }// pages/index.jsimport styles from '../styles/Home.module.css'export default function Home() {return <div className={styles.container}>Home</div>}
Catalyst Implementation:
// src/components/Home/Home.module.css.container { padding: 2rem; }// src/components/Home/Home.jsimport styles from './Home.module.css'export default function Home() {return <div className={styles.container}>Home</div>}
Key Migration Steps:
- Move CSS files to component directories or appropriate locations in the new structure
- Global styles: Place in
/src/static/css/baseand import inclient/styles.js - CSS Modules work out-of-the-box - no configuration needed
- SCSS support is included - just use
.scssfile extensions
For detailed styling options and examples, see: Styling Documentation
Step 10: Image Handling
- Replace
next/imageusage with standard<img>tags or create a custom image component.
Step 11: Font Optimization
Migration Example: Next.js Font to Manual Implementation
Next.js Implementation:
import { Inter, Roboto_Mono } from 'next/font/google'const inter = Inter({subsets: ['latin'],display: 'swap',})const robotoMono = Roboto_Mono({subsets: ['latin'],display: 'swap',})export default function MyApp({ Component, pageProps }) {return (<main className={inter.className}><h1 className={robotoMono.className}>Hello World</h1><Component {...pageProps} /></main>)}
Catalyst Implementation Options:
Option 1: Manual CSS Font Loading
/* src/static/css/base/fonts.css */@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap');:root {--font-inter: 'Inter', sans-serif;--font-roboto-mono: 'Roboto Mono', monospace;}body {font-family: var(--font-inter);}.font-mono {font-family: var(--font-roboto-mono);}
Option 2: React Helmet for Dynamic Font Management
First install React Helmet: npm install react-helmet
import { Helmet } from 'react-helmet'const Component = ({ children }) => {return (<><Helmet><linkhref="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap"rel="stylesheet"/><linkrel="preload"href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"as="style"/></Helmet>{children}</>)}
- React Helmet: react-helmet - Dynamic head management
Step 12: Script Management
Migration Example: Next.js Script to Manual Implementation
Next.js Implementation:
import Script from 'next/script'export default function MyApp({ Component, pageProps }) {return (<><Scriptsrc="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"strategy="afterInteractive"/><Script id="google-analytics" strategy="afterInteractive">{`window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'GA_MEASUREMENT_ID');`}</Script><Component {...pageProps} /></>)}
React Helmet for Dynamic Script Management
Install React Helmet: npm install react-helmet
// src/components/Analytics/Analytics.jsimport { Helmet } from 'react-helmet'const Analytics = ({ measurementId }) => {return (<Helmet>{/* External script with preloading */}<linkrel="preload"href={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}as="script"/><scriptasyncsrc={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}/>{/* Inline script */}<script>{`window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', '${measurementId}');`}</script></Helmet>)}
- React Helmet: react-helmet - Dynamic head management with script support
- React Helmet Async: react-helmet-async - SSR-safe version with better performance
Step 13: Layouts
Migration Example: Global Layout
Next.js Implementation:
// components/layout.jsimport Header from './Header'import Footer from './footer'export default function Layout({ children }) {return (<><Header /><main>{children}</main><Footer /></>)}
Catalyst Implementation:
// src/js/containers/App/index.jsimport React from "react"import { Outlet } from "catalyst-core"import Header from './Header'import Footer from './footer'const App = () => {return (<div><Header /><main><Outlet /></main><Footer /></div>)}
For layout examples, see: Pet Adoption App Layout Guide
Step 14: Metadata and Head Management
Migration Example: Dynamic Metadata
Next.js Implementation:
// pages/user/[id].jsimport Head from 'next/head'export default function UserProfile({ user }) {return (<><Head><title>Profile</title></Head><div>User Profile</div></>)}
Catalyst Implementation:
// src/components/UserProfile/UserProfile.jsconst UserProfile = () => {return <div>User Profile</div>}UserProfile.setMetaData = () => {return [<title key="title">Profile</title>,]}
For detailed metadata management, see: Dynamic Metadata Documentation
Step 15: Environment Variables
Migration Example: Environment Configuration
Next.js Implementation:
// .env.localNEXT_PUBLIC_API_URL=https://api.example.comDATABASE_URL=postgresql://localhost:5432/mydb// Usage in codeconst apiUrl = process.env.NEXT_PUBLIC_API_URLconst dbUrl = process.env.DATABASE_URL
Catalyst Implementation:
// config/config.json{"API_URL": "https://api.example.com","DATABASE_URL": "postgresql://localhost:5432/mydb","CLIENT_ENV_VARIABLES": ["API_URL"]}
// Usage in codeconst apiUrl = process.env.API_URL // Available on both client & serverconst dbUrl = process.env.DATABASE_URL // Server-only (not in CLIENT_ENV_VARIABLES)
Key Migration Steps:
- Move environment variables from
.envfiles toconfig/config.json - Remove
NEXT_PUBLIC_prefixes - useCLIENT_ENV_VARIABLESinstead - List client-accessible variables in the
CLIENT_ENV_VARIABLESarray - Server-only variables should NOT be included in
CLIENT_ENV_VARIABLES
Security note: Variables listed in CLIENT_ENV_VARIABLES are exposed to the client and can be viewed in browser dev tools.
For the complete environment configuration guide, see: Environment Variables Documentation
Step 16: Build and Deployment
-
Update your build script from next build to Catalyst's build process:
npm run build. -
Modify your deployment scripts and configurations to work with the new Catalyst build output.
Step 17: Final Checks
-
Update all import statements to reflect the new project structure.
-
Replace any remaining Next.js specific components or functions with their Catalyst equivalents or standard React/HTML alternatives.
-
Test your application thoroughly to ensure all features work as expected in the new Catalyst environment.