Ders İçeriği

Build Process ve Production Hazırlığı

React uygulamanızı production ortamına dağıtmadan önce, uygulamanızı optimize etmeniz ve production için hazırlamanız gerekir. Bu süreç, uygulamanızın performansını artırmak, dosya boyutlarını küçültmek ve güvenlik açıklarını kapatmak için kritiktir.

Create React App ile oluşturulan projeler, varsayılan olarak güçlü bir build sistemi ile gelir. Bu sistem, Webpack, Babel ve diğer modern araçları kullanarak uygulamanızı production için optimize eder. Build süreci sırasında gerçekleşen optimizasyonlar şunlardır: kod minification (küçültme), dead code elimination (kullanılmayan kodların kaldırılması), bundle splitting (kod parçalama), asset optimization (görsel ve diğer dosyaların optimizasyonu) ve source map oluşturma.

Production Build Oluşturma

Production build oluşturmak için aşağıdaki komutu kullanın:

bash npm run build

Bu komut çalıştırıldığında, Create React App şu işlemleri gerçekleştirir:

# Build süreci detayları

Creating an optimized production build...

Compiled successfully.


File sizes after gzip:


  46.61 KB  build/static/js/2.8e8b8999.chunk.js

  1.78 KB   build/static/js/main.5ecd60bf.chunk.js

  1.17 KB   build/static/js/runtime-main.665d9e0e.js

  1.69 KB   build/static/css/main.5f361e03.chunk.css


The project was built assuming it is hosted at /.

You can control this with the homepage field in your package.json.


The build folder is ready to be deployed.

You may serve it with a static server:


  npm install -g serve

  serve -s build


Find out more about deployment here:

  https://cra.link/deployment

Build süreci tamamlandığında, build klasörü oluşturulur ve bu klasör production için optimize edilmiş dosyaları içerir.

Build Optimizasyonları

Production build sırasında gerçekleşen optimizasyonları anlamak önemlidir:

// package.json - Build scripts ve konfigürasyon

{

  "name": "my-react-app",

  "version": "0.1.0",

  "private": true,

  "homepage": "https://myusername.github.io/my-react-app",

  "dependencies": {

    "react": "^18.2.0",

    "react-dom": "^18.2.0",

    "react-scripts": "5.0.1"

  },

  "scripts": {

    "start": "react-scripts start",

    "build": "react-scripts build",

    "test": "react-scripts test",

    "eject": "react-scripts eject",

    "analyze": "npm run build && npx webpack-bundle-analyzer build/static/js/*.js"

  },

  "eslintConfig": {

    "extends": [

      "react-app",

      "react-app/jest"

    ]

  },

  "browserslist": {

    "production": [

      ">0.2%",

      "not dead",

      "not op_mini all"

    ],

    "development": [

      "last 1 chrome version",

      "last 1 firefox version",

      "last 1 safari version"

    ]

  }

}

Environment Variables

Production ortamında farklı konfigürasyonlar kullanmak için environment variables kullanabilirsiniz:

# .env.production

REACT_APP_API_URL=https://api.myapp.com

REACT_APP_ANALYTICS_ID=GA-XXXXXXXXX

REACT_APP_VERSION=$npm_package_version

GENERATE_SOURCEMAP=false

// src/config/environment.js

const config = {

  development: {

    apiUrl: 'http://localhost:3001/api',

    analyticsId: null,

    debug: true

  },

  production: {

    apiUrl: process.env.REACT_APP_API_URL || 'https://api.myapp.com',

    analyticsId: process.env.REACT_APP_ANALYTICS_ID,

    debug: false

  }

};


const environment = process.env.NODE_ENV || 'development';


export default config[environment];


// Kullanım

import config from './config/environment';


// API çağrıları

const apiClient = axios.create({

  baseURL: config.apiUrl,

  timeout: 10000

});


// Analytics

if (config.analyticsId) {

  // Google Analytics initialization

  gtag('config', config.analyticsId);

}

Performance Optimizasyonları

Production build öncesi uygulamanızı optimize etmek için çeşitli teknikler kullanabilirsiniz:

// Code splitting with React.lazy

import React, { Suspense, lazy } from 'react';

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

import LoadingSpinner from './components/LoadingSpinner';


// Lazy load components

const Home = lazy(() => import('./pages/Home'));

const About = lazy(() => import('./pages/About'));

const Dashboard = lazy(() => import('./pages/Dashboard'));

const Profile = lazy(() => import('./pages/Profile'));


function App() {

  return (

    <Router>

      <Suspense fallback={<LoadingSpinner />}>

        <Routes>

          <Route path="/" element={<Home />} />

          <Route path="/about" element={<About />} />

          <Route path="/dashboard" element={<Dashboard />} />

          <Route path="/profile" element={<Profile />} />

        </Routes>

      </Suspense>

    </Router>

  );

}


// Image optimization

import { useState, useCallback } from 'react';


function OptimizedImage({ src, alt, className, ...props }) {

  const [loaded, setLoaded] = useState(false);

  const [error, setError] = useState(false);

  

  const handleLoad = useCallback(() => {

    setLoaded(true);

  }, []);

  

  const handleError = useCallback(() => {

    setError(true);

  }, []);

  

  if (error) {

    return (

      <div className={`image-placeholder ${className}`}>

        <span>Image failed to load</span>

      </div>

    );

  }

  

  return (

    <div className={`image-container ${className}`}>

      {!loaded && <div className="image-skeleton" />}

      <img

        src={src}

        alt={alt}

        onLoad={handleLoad}

        onError={handleError}

        style={{ display: loaded ? 'block' : 'none' }}

        loading="lazy"

        {...props}

      />

    </div>

  );

}


// Memoization for expensive calculations

import { useMemo, memo } from 'react';


const ExpensiveComponent = memo(({ data, filter }) => {

  const processedData = useMemo(() => {

    return data

      .filter(item => item.category === filter)

      .sort((a, b) => b.score - a.score)

      .slice(0, 100);

  }, [data, filter]);

  

  return (

    <div>

      {processedData.map(item => (

        <div key={item.id}>{item.name}</div>

      ))}

    </div>

  );

});


// Service Worker for caching

// public/sw.js

const CACHE_NAME = 'my-app-v1';

const urlsToCache = [

  '/',

  '/static/js/bundle.js',

  '/static/css/main.css',

  '/manifest.json'

];


self.addEventListener('install', (event) => {

  event.waitUntil(

    caches.open(CACHE_NAME)

      .then((cache) => {

        return cache.addAll(urlsToCache);

      })

  );

});


self.addEventListener('fetch', (event) => {

  event.respondWith(

    caches.match(event.request)

      .then((response) => {

        if (response) {

          return response;

        }

        return fetch(event.request);

      })

  );

});


// Service Worker registration

// src/index.js

import React from 'react';

import ReactDOM from 'react-dom/client';

import App from './App';


const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(<App />);


// Register service worker

if ('serviceWorker' in navigator) {

  window.addEventListener('load', () => {

    navigator.serviceWorker.register('/sw.js')

      .then((registration) => {

        console.log('SW registered: ', registration);

      })

      .catch((registrationError) => {

        console.log('SW registration failed: ', registrationError);

      });

  });

}

Hosting Seçenekleri

React uygulamanızı dağıtmak için birçok hosting seçeneği bulunur. Her seçeneğin kendine özgü avantajları ve kullanım senaryoları vardır.

Netlify

Netlify, static site hosting için popüler bir platformdur. Git repository'nizi bağlayarak otomatik deployment sağlar.

# Netlify CLI kurulumu

npm install -g netlify-cli


# Login

netlify login


# Build ve deploy

netlify build

netlify deploy --prod


# Netlify konfigürasyonu

# netlify.toml

[build]

  publish = "build"

  command = "npm run build"


[build.environment]

  REACT_APP_API_URL = "https://api.myapp.com"


[[redirects]]

  from = "/*"

  to = "/index.html"

  status = 200


[[headers]]

  for = "/static/*"

  [headers.values]

    Cache-Control = "public, max-age=31536000, immutable"


[[headers]]

  for = "/*.js"

  [headers.values]

    Cache-Control = "public, max-age=31536000, immutable"


[[headers]]

  for = "/*.css"

  [headers.values]

    Cache-Control = "public, max-age=31536000, immutable"

Vercel

Vercel, özellikle React ve Next.js uygulamaları için optimize edilmiş bir platformdur.

# Vercel CLI kurulumu

npm install -g vercel


# Deploy

vercel


# Production deploy

vercel --prod


# Vercel konfigürasyonu

# vercel.json

{

  "version": 2,

  "builds": [

    {

      "src": "package.json",

      "use": "@vercel/static-build",

      "config": {

        "distDir": "build"

      }

    }

  ],

  "routes": [

    {

      "src": "/static/(.*)",

      "headers": {

        "cache-control": "public, max-age=31536000, immutable"

      }

    },

    {

      "src": "/(.*)",

      "dest": "/index.html"

    }

  ],

  "env": {

    "REACT_APP_API_URL": "https://api.myapp.com"

  }

}

GitHub Pages

GitHub Pages, GitHub repository'nizden doğrudan static site hosting sağlar.

# gh-pages paketi kurulumu

npm install --save-dev gh-pages


# package.json'a script ekleme

{

  "scripts": {

    "predeploy": "npm run build",

    "deploy": "gh-pages -d build"

  },

  "homepage": "https://yourusername.github.io/your-repo-name"

}


# Deploy

npm run deploy

# GitHub Actions ile otomatik deployment

# .github/workflows/deploy.yml

name: Deploy to GitHub Pages


on:

  push:

    branches: [ main ]


jobs:

  build-and-deploy:

    runs-on: ubuntu-latest

    

    steps:

    - name: Checkout

      uses: actions/checkout@v3

      

    - name: Setup Node.js

      uses: actions/setup-node@v3

      with:

        node-version: '18'

        cache: 'npm'

        

    - name: Install dependencies

      run: npm ci

      

    - name: Run tests

      run: npm test -- --coverage --watchAll=false

      

    - name: Build

      run: npm run build

      env:

        REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}

        

    - name: Deploy

      uses: peaceiris/actions-gh-pages@v3

      with:

        github_token: ${{ secrets.GITHUB_TOKEN }}

        publish_dir: ./build# AWS CLI kurulumu ve konfigürasyonu

aws configure


# S3 bucket oluşturma

aws s3 mb s3://my-react-app-bucket


# Build dosyalarını S3'e yükleme

aws s3 sync build/ s3://my-react-app-bucket --delete


# Bucket policy

{

  "Version": "2012-10-17",

  "Statement": [

    {

      "Sid": "PublicReadGetObject",

      "Effect": "Allow",

      "Principal": "*",

      "Action": "s3:GetObject",

      "Resource": "arn:aws:s3:::my-react-app-bucket/*"

    }

  ]

}


# CloudFormation template

# infrastructure.yml

AWSTemplateFormatVersion: '2010-09-09'

Description: 'React App Infrastructure'


Resources:

  S3Bucket:

    Type: AWS::S3::Bucket

    Properties:

      BucketName: my-react-app-bucket

      WebsiteConfiguration:

        IndexDocument: index.html

        ErrorDocument: index.html

      PublicAccessBlockConfiguration:

        BlockPublicAcls: false

        BlockPublicPolicy: false

        IgnorePublicAcls: false

        RestrictPublicBuckets: false


  CloudFrontDistribution:

    Type: AWS::CloudFront::Distribution

    Properties:

      DistributionConfig:

        Origins:

          - DomainName: !GetAtt S3Bucket.RegionalDomainName

            Id: S3Origin

            S3OriginConfig:

              OriginAccessIdentity: ''

        Enabled: true

        DefaultRootObject: index.html

        CustomErrorResponses:

          - ErrorCode: 404

            ResponseCode: 200

            ResponsePagePath: /index.html

        DefaultCacheBehavior:

          TargetOriginId: S3Origin

          ViewerProtocolPolicy: redirect-to-https

          CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad

        PriceClass: PriceClass_100


Outputs:

  CloudFrontURL:

    Description: 'CloudFront Distribution URL'

    Value: !GetAtt CloudFrontDistribution.DomainName

Docker ile Containerization

Docker kullanarak uygulamanızı containerize edebilir ve herhangi bir ortamda çalıştırabilirsiniz.

# Dockerfile

# Multi-stage build

FROM node:18-alpine as build


WORKDIR /app


# Copy package files

COPY package*.json ./


# Install dependencies

RUN npm ci --only=production


# Copy source code

COPY . .


# Build the app

RUN npm run build


# Production stage

FROM nginx:alpine


# Copy build files

COPY --from=build /app/build /usr/share/nginx/html


# Copy nginx configuration

COPY nginx.conf /etc/nginx/nginx.conf


# Expose port

EXPOSE 80


# Start nginx

CMD ["nginx", "-g", "daemon off;"]

# nginx.conf

events {

    worker_connections 1024;

}


http {

    include       /etc/nginx/mime.types;

    default_type  application/octet-stream;


    server {

        listen 80;

        server_name localhost;

        root /usr/share/nginx/html;

        index index.html;


        # Gzip compression

        gzip on;

        gzip_vary on;

        gzip_min_length 1024;

        gzip_types

            text/plain

            text/css

            text/xml

            text/javascript

            application/javascript

            application/xml+rss

            application/json;


        # Cache static assets

        location /static/ {

            expires 1y;

            add_header Cache-Control "public, immutable";

        }


        # Handle client-side routing

        location / {

            try_files $uri $uri/ /index.html;

        }


        # Security headers

        add_header X-Frame-Options "SAMEORIGIN" always;

        add_header X-Content-Type-Options "nosniff" always;

        add_header X-XSS-Protection "1; mode=block" always;

        add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    }

}

# Docker build ve run

docker build -t my-react-app .

docker run -p 80:80 my-react-app


# Docker Compose

# docker-compose.yml

version: '3.8'


services:

  web:

    build: .

    ports:

      - "80:80"

    environment:

      - NODE_ENV=production

    restart: unless-stopped


  # Optional: Add a reverse proxy

  nginx:

    image: nginx:alpine

    ports:

      - "443:443"

    volumes:

      - ./ssl:/etc/nginx/ssl

      - ./nginx-proxy.conf:/etc/nginx/nginx.conf

    depends_on:

      - web

    restart: unless-stopped

CI/CD Pipeline

Continuous Integration ve Continuous Deployment (CI/CD) pipeline'ları, kodunuzun otomatik olarak test edilmesi, build edilmesi ve deploy edilmesi için kritiktir.

GitHub Actions

GitHub Actions ile kapsamlı bir CI/CD pipeline oluşturabilirsiniz:

# .github/workflows/ci-cd.yml

name: CI/CD Pipeline


on:

  push:

    branches: [ main, develop ]

  pull_request:

    branches: [ main ]


env:

  NODE_VERSION: '18'

  REGISTRY: ghcr.io

  IMAGE_NAME: ${{ github.repository }}


jobs:

  test:

    runs-on: ubuntu-latest

    

    steps:

    - name: Checkout code

      uses: actions/checkout@v3

      

    - name: Setup Node.js

      uses: actions/setup-node@v3

      with:

        node-version: ${{ env.NODE_VERSION }}

        cache: 'npm'

        

    - name: Install dependencies

      run: npm ci

      

    - name: Run linting

      run: npm run lint

      

    - name: Run tests

      run: npm test -- --coverage --watchAll=false

      

    - name: Upload coverage to Codecov

      uses: codecov/codecov-action@v3

      with:

        file: ./coverage/lcov.info

        

  build:

    needs: test

    runs-on: ubuntu-latest

    

    steps:

    - name: Checkout code

      uses: actions/checkout@v3

      

    - name: Setup Node.js

      uses: actions/setup-node@v3

      with:

        node-version: ${{ env.NODE_VERSION }}

        cache: 'npm'

        

    - name: Install dependencies

      run: npm ci

      

    - name: Build application

      run: npm run build

      env:

        REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}

        REACT_APP_ANALYTICS_ID: ${{ secrets.REACT_APP_ANALYTICS_ID }}

        

    - name: Upload build artifacts

      uses: actions/upload-artifact@v3

      with:

        name: build-files

        path: build/

        

  deploy-staging:

    needs: build

    runs-on: ubuntu-latest

    if: github.ref == 'refs/heads/develop'

    environment: staging

    

    steps:

    - name: Download build artifacts

      uses: actions/download-artifact@v3

      with:

        name: build-files

        path: build/

        

    - name: Deploy to staging

      run: |

        # Deploy to staging environment

        echo "Deploying to staging..."

        # Your staging deployment commands here

        

  deploy-production:

    needs: build

    runs-on: ubuntu-latest

    if: github.ref == 'refs/heads/main'

    environment: production

    

    steps:

    - name: Download build artifacts

      uses: actions/download-artifact@v3

      with:

        name: build-files

        path: build/

        

    - name: Deploy to production

      run: |

        # Deploy to production environment

        echo "Deploying to production..."

        # Your production deployment commands here

        

  docker:

    needs: test

    runs-on: ubuntu-latest

    if: github.ref == 'refs/heads/main'

    

    steps:

    - name: Checkout code

      uses: actions/checkout@v3

      

    - name: Log in to Container Registry

      uses: docker/login-action@v2

      with:

        registry: ${{ env.REGISTRY }}

        username: ${{ github.actor }}

        password: ${{ secrets.GITHUB_TOKEN }}

        

    - name: Extract metadata

      id: meta

      uses: docker/metadata-action@v4

      with:

        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

        tags: |

          type=ref,event=branch

          type=ref,event=pr

          type=sha

          

    - name: Build and push Docker image

      uses: docker/build-push-action@v4

      with:

        context: .

        push: true

        tags: ${{ steps.meta.outputs.tags }}

        labels: ${{ steps.meta.outputs.labels }}

GitLab CI/CD

GitLab CI/CD ile pipeline oluşturma:

# .gitlab-ci.yml

stages:

  - test

  - build

  - deploy


variables:

  NODE_VERSION: "18"

  DOCKER_DRIVER: overlay2


cache:

  paths:

    - node_modules/


test:

  stage: test

  image: node:$NODE_VERSION

  script:

    - npm ci

    - npm run lint

    - npm test -- --coverage --watchAll=false

  artifacts:

    reports:

      coverage_report:

        coverage_format: cobertura

        path: coverage/cobertura-coverage.xml

  coverage: '/Lines\s*:\s*(\d+\.\d+)%/'


build:

  stage: build

  image: node:$NODE_VERSION

  script:

    - npm ci

    - npm run build

  artifacts:

    paths:

      - build/

    expire_in: 1 hour

  only:

    - main

    - develop


deploy_staging:

  stage: deploy

  image: alpine:latest

  script:

    - echo "Deploying to staging..."

    # Your staging deployment commands

  environment:

    name: staging

    url: https://staging.myapp.com

  only:

    - develop


deploy_production:

  stage: deploy

  image: alpine:latest

  script:

    - echo "Deploying to production..."

    # Your production deployment commands

  environment:

    name: production

    url: https://myapp.com

  when: manual

  only:

    - main


docker_build:

  stage: build

  image: docker:latest

  services:

    - docker:dind

  script:

    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .

    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

  only:

    - main

React uygulamanızı production ortamına dağıtmak, dikkatli planlama ve doğru araçların seçilmesini gerektirir. Build optimizasyonları, uygun hosting platformu seçimi ve otomatik deployment pipeline'ları, uygulamanızın başarılı bir şekilde kullanıcılara ulaşmasını sağlar. Her hosting seçeneğinin kendine özgü avantajları vardır ve projenizin gereksinimlerine göre en uygun olanını seçmelisiniz.