ntrn
Version:
🚀 Professional AI-powered Next.js to React Native converter with intelligent screen creation, never-fail conversion system, comprehensive auto-fix, automatic runtime error fixing, AI assistant, Mistral AI problem-solving, intelligent Shadcn/ui conversion
388 lines (328 loc) • 11.9 kB
JavaScript
import chalk from 'chalk';
export class SSRHandler {
constructor() {
this.ssrPatterns = [
'getServerSideProps',
'getStaticProps',
'getStaticPaths',
'getInitialProps'
];
this.csrPatterns = [
'useRouter',
'next/router',
'next/link',
'next/head',
'next/image',
'next/dynamic'
];
}
detectSSRCSRPatterns(content) {
const detectedPatterns = {
ssr: [],
csr: [],
hasDataFetching: false,
hasRouting: false,
hasHeadManagement: false,
hasImageOptimization: false,
hasDynamicImports: false
};
// Detect SSR patterns
this.ssrPatterns.forEach(pattern => {
if (content.includes(pattern)) {
detectedPatterns.ssr.push(pattern);
detectedPatterns.hasDataFetching = true;
}
});
// Detect CSR patterns
this.csrPatterns.forEach(pattern => {
if (content.includes(pattern)) {
detectedPatterns.csr.push(pattern);
if (pattern.includes('router') || pattern.includes('link')) {
detectedPatterns.hasRouting = true;
}
if (pattern.includes('head')) {
detectedPatterns.hasHeadManagement = true;
}
if (pattern.includes('image')) {
detectedPatterns.hasImageOptimization = true;
}
if (pattern.includes('dynamic')) {
detectedPatterns.hasDynamicImports = true;
}
}
});
return detectedPatterns;
}
convertSSRToReactNative(content, fileName) {
console.log(chalk.cyan(`🔄 Converting SSR/CSR patterns in ${fileName}...`));
let convertedContent = content;
const patterns = this.detectSSRCSRPatterns(content);
if (patterns.ssr.length > 0 || patterns.csr.length > 0) {
console.log(chalk.yellow(` 📋 Found ${patterns.ssr.length} SSR and ${patterns.csr.length} CSR patterns`));
}
// Convert SSR data fetching
convertedContent = this.convertDataFetching(convertedContent, patterns);
// Convert routing
convertedContent = this.convertRouting(convertedContent, patterns);
// Convert head management
convertedContent = this.convertHeadManagement(convertedContent, patterns);
// Convert image optimization
convertedContent = this.convertImageOptimization(convertedContent, patterns);
// Convert dynamic imports
convertedContent = this.convertDynamicImports(convertedContent, patterns);
return {
content: convertedContent,
convertedPatterns: patterns,
additionalDependencies: this.getAdditionalDependencies(patterns)
};
}
convertDataFetching(content, patterns) {
// Convert getServerSideProps to useEffect with API calls
content = content.replace(
/export\s+async\s+function\s+getServerSideProps\s*\([^)]*\)\s*{[\s\S]*?return\s*{[\s\S]*?props:\s*([^}]*)}[\s\S]*?}/g,
(match, propsContent) => {
return `
// Converted from getServerSideProps
// Original SSR data fetching converted to client-side useEffect
const useFetchData = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
// TODO: Replace with your actual API endpoint
const response = await fetch('YOUR_API_ENDPOINT');
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
return { data, loading, error };
};`;
}
);
// Convert getStaticProps to useEffect
content = content.replace(
/export\s+async\s+function\s+getStaticProps\s*\([^)]*\)\s*{[\s\S]*?return\s*{[\s\S]*?props:\s*([^}]*)}[\s\S]*?}/g,
`
// Converted from getStaticProps
// Original static generation converted to client-side data fetching
const useStaticData = () => {
const [data, setData] = useState(null);
useEffect(() => {
// TODO: Replace with your data source
const loadStaticData = async () => {
// This was originally generated at build time
// Now loads on component mount
const staticData = {}; // Your static data here
setData(staticData);
};
loadStaticData();
}, []);
return data;
};`
);
// Update component to use hooks instead of props
if (patterns.hasDataFetching) {
content = content.replace(
/export\s+default\s+function\s+(\w+)\s*\(\s*{\s*([^}]+)\s*}\s*\)/,
(match, componentName, props) => {
return `export default function ${componentName}() {
const { data, loading, error } = useFetchData();
if (loading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#007AFF" />
<Text>Loading...</Text>
</View>
);
}
if (error) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ color: 'red' }}>Error: {error.message}</Text>
</View>
);
}`;
}
);
}
return content;
}
convertRouting(content, patterns) {
// Convert next/router to React Navigation
content = content.replace(
/import\s+{\s*useRouter\s*}\s+from\s+['"]next\/router['"];?/g,
`import { useNavigation, useRoute } from '@react-navigation/native';`
);
content = content.replace(
/const\s+router\s*=\s*useRouter\(\);?/g,
`const navigation = useNavigation();
const route = useRoute();`
);
// Convert router.push to navigation.navigate
content = content.replace(
/router\.push\(['"`]([^'"`]+)['"`]\)/g,
`navigation.navigate('$1')`
);
// Convert router.query to route.params
content = content.replace(/router\.query/g, 'route.params');
// Convert Link components
content = content.replace(
/import\s+Link\s+from\s+['"]next\/link['"];?/g,
`import { TouchableOpacity, Text } from 'react-native';
import { useNavigation } from '@react-navigation/native';`
);
content = content.replace(
/<Link\s+href=['"`]([^'"`]+)['"`][^>]*>([\s\S]*?)<\/Link>/g,
(match, href, children) => {
return `<TouchableOpacity onPress={() => navigation.navigate('${href}')}>
${children}
</TouchableOpacity>`;
}
);
return content;
}
convertHeadManagement(content, patterns) {
// Convert next/head to react-native-helmet-async or remove
content = content.replace(
/import\s+Head\s+from\s+['"]next\/head['"];?/g,
`// Head management not needed in React Native
// Original Next.js Head component removed`
);
content = content.replace(
/<Head>[\s\S]*?<\/Head>/g,
`{/* Head content removed - not applicable in React Native */}`
);
return content;
}
convertImageOptimization(content, patterns) {
// Convert next/image to expo-image or react-native Image
content = content.replace(
/import\s+Image\s+from\s+['"]next\/image['"];?/g,
`import { Image } from 'expo-image';`
);
// Update Image props for React Native
content = content.replace(
/<Image\s+([^>]*?)src=['"`]([^'"`]+)['"`]([^>]*?)\/?>|<Image\s+([^>]*?)src=['"`]([^'"`]+)['"`]([^>]*?)>([\s\S]*?)<\/Image>/g,
(match, props1, src1, props2, props3, src2, props4, children) => {
const src = src1 || src2;
const props = (props1 || '') + (props2 || '') + (props3 || '') + (props4 || '');
// Extract width and height if present
const widthMatch = props.match(/width={?(\d+)}?/);
const heightMatch = props.match(/height={?(\d+)}?/);
const altMatch = props.match(/alt=['"`]([^'"`]*)['"`]/);
const width = widthMatch ? widthMatch[1] : '100';
const height = heightMatch ? heightMatch[1] : '100';
const alt = altMatch ? altMatch[1] : 'Image';
return `<Image
source={{ uri: '${src}' }}
style={{ width: ${width}, height: ${height} }}
contentFit="cover"
alt="${alt}"
/>`;
}
);
return content;
}
convertDynamicImports(content, patterns) {
// Convert next/dynamic to React.lazy
content = content.replace(
/import\s+dynamic\s+from\s+['"]next\/dynamic['"];?/g,
`import React, { Suspense } from 'react';
import { View, ActivityIndicator } from 'react-native';`
);
content = content.replace(
/const\s+(\w+)\s*=\s*dynamic\(\s*\(\)\s*=>\s*import\(['"`]([^'"`]+)['"`]\)[^)]*\);?/g,
(match, componentName, importPath) => {
return `const ${componentName} = React.lazy(() => import('${importPath}'));
const ${componentName}WithSuspense = (props) => (
<Suspense fallback={
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#007AFF" />
</View>
}>
<${componentName} {...props} />
</Suspense>
);`;
}
);
return content;
}
getAdditionalDependencies(patterns) {
const dependencies = {};
if (patterns.hasDataFetching) {
dependencies['@react-native-async-storage/async-storage'] = '^1.19.0';
}
if (patterns.hasRouting) {
dependencies['@react-navigation/native'] = '^6.1.0';
dependencies['@react-navigation/native-stack'] = '^6.9.0';
}
if (patterns.hasImageOptimization) {
dependencies['expo-image'] = '^1.3.0';
}
return dependencies;
}
generateConversionReport(conversions) {
const report = {
totalFilesProcessed: conversions.length,
ssrConversions: 0,
csrConversions: 0,
patternsSummary: {
dataFetching: 0,
routing: 0,
headManagement: 0,
imageOptimization: 0,
dynamicImports: 0
},
recommendations: []
};
conversions.forEach(conversion => {
if (conversion.convertedPatterns.ssr.length > 0) {
report.ssrConversions++;
}
if (conversion.convertedPatterns.csr.length > 0) {
report.csrConversions++;
}
if (conversion.convertedPatterns.hasDataFetching) {
report.patternsSummary.dataFetching++;
}
if (conversion.convertedPatterns.hasRouting) {
report.patternsSummary.routing++;
}
if (conversion.convertedPatterns.hasHeadManagement) {
report.patternsSummary.headManagement++;
}
if (conversion.convertedPatterns.hasImageOptimization) {
report.patternsSummary.imageOptimization++;
}
if (conversion.convertedPatterns.hasDynamicImports) {
report.patternsSummary.dynamicImports++;
}
});
// Generate recommendations
if (report.patternsSummary.dataFetching > 0) {
report.recommendations.push(
'Review data fetching patterns: SSR/getStaticProps converted to useEffect. Consider implementing proper loading states and error handling.'
);
}
if (report.patternsSummary.routing > 0) {
report.recommendations.push(
'Update navigation: Next.js routing converted to React Navigation. Test navigation flows and update deep linking if needed.'
);
}
if (report.patternsSummary.headManagement > 0) {
report.recommendations.push(
'Head management removed: Next.js Head components are not applicable in React Native. Consider using Expo constants for app metadata.'
);
}
return report;
}
}