Notice
Recent Posts
Recent Comments
Link
ยซ   2024/05   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
๊ด€๋ฆฌ ๋ฉ”๋‰ด

Lennon FE

[Next.js 13] Emotion์œผ๋กœ app router RSC๋ฅผ ๊ตฌ์„ฑํ•ด๋ณด์ž ๋ณธ๋ฌธ

๐Ÿง‘‍๐Ÿ’ป Web/React

[Next.js 13] Emotion์œผ๋กœ app router RSC๋ฅผ ๊ตฌ์„ฑํ•ด๋ณด์ž

Lennon 2023. 7. 17. 23:28
728x90
๋ฐ˜์‘ํ˜•

Next13์˜ app router ๋ฐฉ์‹์—์„œ ํ˜„์žฌ Emotion์„ ๊ณต์‹์ ์œผ๋กœ ์ง€์›ํ•ด์ฃผ์ง€ ์•Š๋Š”๋‹ค.๐Ÿฅฒ

๊ทธ๋ ‡์ง€๋งŒ Project์—์„œ Emotion lib๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , css์†์„ฑ์„ ์‚ฌ์šฉํ•˜๊ณ , app router์˜ RSC๋ฅผ  ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ๋“ค์ด ์žˆ์„๊นŒ ๋ด(๋ณธ์ธ ํฌํ•จ!)

๋ฐฉ๋ฒ•์„ ์•Œ๋ ค์ฃผ๋ คํ•œ๋‹ค.

 

๋‹ค๋“ค ์•Œ๋‹ค์‹œํ”ผ Emotion์— css ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด tsconfig.json์— 

"jsxImportSource": "@emotion/react",

์œ„์™€ ๊ฐ™์€ ์†์„ฑ์„ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด ๋ชจ๋“  ํƒœ๊ทธ์— css๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ์–ด ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

import { css } from '@emotion/react';

<div
    ref={sectionImageRef}
    css={css`
    	width: 240px;
    `}
>
    <Image
        src={'/pngwing.png'}
        width={800}
        height={450}
        alt="main"
    />
</div>

๊ทธ๋ ‡์ง€๋งŒ tsconfig์—

"jsxImportSource": "@emotion/react",

์œ„ ์†์„ฑ์„ ๊ฐ€์ง„ ํƒœ๊ทธ๋Š” ๋ชจ๋‘ client ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

์ด์œ ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ํƒœ๊ทธ์— css๊ฐ€ optional๋กœ ๋“ค์–ด๊ฐ€ ๋ฒ„๋ฆฐ๋‹ค.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด app router ๋ฐฉ์‹์—์„œ ์„œ๋ฒ„์‚ฌ์ด๋“œ๋ Œ๋”๋ง์„ ์ „ํ˜€ ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋œ๋‹ค.

์ปดํŒŒ์ผํ•  ๋•Œ ์• ์ดˆ์— emotionํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์—

// ex) app/auth/signIn/page.tsx

// โŒ Error => use client๋ฅผ ๋ถ™์ด์„ธ์š”! ๋ผ๋Š” ์—๋Ÿฌ๋ฅผ ๋ฑ‰์Œ

const SignIn = () => {
    return <div>1234</div>
};

export default SignIn;


// โœ… OK but No RSC๐Ÿ˜‡
"use client";

const SignIn = () => {
    return <div>1234</div>
};

export default SignIn;

์œ„์™€ ๊ฐ™์€ ๊ธฐ๋ณธ์ ์ธ ํƒœ๊ทธ์—๋„ "use client"๋ฅผ ๋ถ™์—ฌ์ค˜์•ผ ํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด Emotion์„ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋Š” app router ๋ฐฉ์‹์˜ RSC๋ฅผ ํ™œ์šฉํ•˜์ง€ ๋ชปํ• ๊นŒ?
์•„๋‹ˆ๋‹ค! ๋ฐฉ๋ฒ•์€ ์žˆ๋‹ค :)

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

next.config.js์—์„œ webpack ์ปค์Šคํ…€ ์„ค์ •์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

// next.config.js

/** @type {import('next').NextConfig} */

const enableEmotionRSC = require('./enableEmotionRSC');

const nextConfig = enableEmotionRSC({
    reactStrictMode: true,
    experimental: { appDir: true },
    swcMinify: true,
    output: 'standalone',
    async rewrites() {
        return [
            {
                source: '/api/:path*',
                destination: `http://${process.env.REACT_APP_SERVER_DOMAIN_URL}/api/:path*`
            }
        ];
    }
});

module.exports = nextConfig;

์œ„๋Š” ๋‚ด nextConfig ์„ค์ •์ด๋‹ค.

๋‹ค๋ฅธ ๊ฑฐ๋Š” ๋ฌด์‹œํ•˜๊ณ  enableEmotionRSC๋ฅผ ์‚ดํŽด๋ณด์ž.

// enableEmotionRSC.js

const path = require('path');

const includedDirs = [path.resolve(__dirname, 'src')];

module.exports = function enableEmotionRSC(nextConfig) {
    return {
        ...nextConfig,
        webpack: (config, { isServer }) => {
            config.module.rules = config.module.rules.filter(
                (rule) =>
                    !(rule.test && rule.test.test && rule.test.test('.tsx'))
            );

            config.module.rules.push({
                test: /\.tsx?$/,
                include: includedDirs,
                use: [
                    {
                        loader: 'ts-loader',
                        options: {
                            transpileOnly: true,
                            configFile: path.resolve(__dirname, 'tsconfig.json')
                        }
                    }
                ]
            });

            if (isServer) {
                config.resolve.alias['@emotion/react'] = 'react';
            }

            return config;
        }
    };
};

nextConfig๋ฅผ ์ธ์ž๋กœ ๋ฐ›๊ณ , ์ถ”๊ฐ€์ ์œผ๋กœ webpack ์„ค์ •์„ ํ•ด์ค€๋‹ค.

webpack์˜ options์—์„  dev, isServer ๋“ฑ๋“ฑ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

๋‚˜๋Š” isServer ๋ฐ์ดํ„ฐ๋งŒ ์ผ๋‹จ ํ•„์š”ํ•ด์„œ ํ•ด๋‹น ๊ฐ’๋งŒ ๊ฐ€์ ธ์™”๋‹ค.

 

๊ธฐ๋ณธ์ ์œผ๋กœ ts-loader๋ฅผ ์‚ฌ์šฉํ•ด tsconfig๋ฅผ reslove ํ•ด์ฃผ๊ณ ,

 

๊ทธ ๋’ค์— 

if (isServer) {
	config.resolve.alias['@emotion/react'] = 'react';
}

์œ„์—์„œ isServer๋ฅผ ๋ถ„๊ธฐ๋กœ ๊ฐ’์„ ์„ค์ •ํ•ด ์ค€๋‹ค

์„œ๋ฒ„์‚ฌ์ด๋“œ๋ Œ๋”๋ง(SSR)์„ ํ•  ๋•Œ๋Š” @emotion/react์˜ ๊ฐ’์„ react๋กœ ๋Œ€์ฒดํ•ด ์ค€๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

 

๋ฌผ๋ก  ๋‹น์—ฐํ•˜๊ฒŒ๋„ emotion์„ ์‚ฌ์šฉํ•˜๋‹ˆ tsconfig.json์—

"jsxImportSource": "@emotion/react",

์œ„์™€ ๊ฐ™์€ ๊ฐ’์ด ๋“ค์–ด๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด emotion์„ ํ™œ์šฉํ•˜๋ฉด์„œ, app router ๋ฐฉ์‹์˜ RSC ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

์ „์ฒด ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

์ถ”๊ฐ€์ ์ธ eslint์„ค์ •์€ ๋ณธ์ธ ์ž…๋ง›์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค!

 

// root/tsconfig.json

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "preserve",
        "incremental": true,
        "baseUrl": ".",
        "paths": {
            "@/*": ["./src/*"]
        },
        "jsxImportSource": "@emotion/react",
        "plugins": [
            {
                "name": "next"
            }
        ]
    },
    "include": [
        "next-env.d.ts",
        "**/*.ts",
        "**/*.tsx",
        "cypress/**/*",
        "**/*.cy.ts",
        ".next/types/**/*.ts",
        "enableEmotionRSC.js"
    ],
    "exclude": ["node_modules"]
}
// root/enableEmotionRSC.js

const path = require('path');

const includedDirs = [path.resolve(__dirname, 'src')];

module.exports = function enableEmotionRSC(nextConfig) {
    return {
        ...nextConfig,
        webpack: (config, { isServer }) => {
            config.module.rules = config.module.rules.filter(
                (rule) =>
                    !(rule.test && rule.test.test && rule.test.test('.tsx'))
            );

            config.module.rules.push({
                test: /\.tsx?$/,
                include: includedDirs,
                use: [
                    {
                        loader: 'ts-loader',
                        options: {
                            transpileOnly: true,
                            configFile: path.resolve(__dirname, 'tsconfig.json')
                        }
                    }
                ]
            });

            if (isServer) {
                config.resolve.alias['@emotion/react'] = 'react';
            }

            return config;
        }
    };
};
// root/next.config.js

/** @type {import('next').NextConfig} */

const enableEmotionRSC = require('./enableEmotionRSC');

const nextConfig = enableEmotionRSC({
    reactStrictMode: true,
    experimental: { appDir: true },
    swcMinify: true,
    output: 'standalone',
    async rewrites() {
        return [
            {
                source: '/api/:path*',
                destination: `http://${process.env.REACT_APP_SERVER_DOMAIN_URL}/api/:path*`
            }
        ];
    }
});

module.exports = nextConfig;

 

728x90
๋ฐ˜์‘ํ˜•
Comments