Razzle: μ„œλ²„ λ Œλ”λ§ 문제λ₯Ό ν•΄κ²°ν•˜λ €λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Ό ν•©λ‹ˆκΉŒ?

에 λ§Œλ“  2019λ…„ 02μ›” 05일  Β·  9μ½”λ©˜νŠΈ  Β·  좜처: jaredpalmer/razzle

μ•ˆλ…•ν•˜μ‹­λ‹ˆκΉŒ,

Google의 검색 μ½˜μ†”μ— λ‘œκ·ΈμΈν•˜κ³  Google둜 κ°€μ Έμ˜€λ©΄ μ›Ήμ‚¬μ΄νŠΈκ°€ 빈 νŽ˜μ΄μ§€λ‘œ λ Œλ”λ§λ˜λŠ” 문제λ₯Ό λ°œκ²¬ν–ˆμŠ΅λ‹ˆλ‹€.

λ‚˜λŠ” μ•½κ°„μ˜ 연ꡬλ₯Ό ν–ˆκ³  κ·Έ 쀑 μΌλΆ€λŠ” λ‚˜λ₯Ό 이 κΈ°μ‚¬λ‘œ μ΄λŒμ—ˆμŠ΅λ‹ˆλ‹€.

https://medium.com/@gajus/react -application-seen-as-a-blank-page-via-fetch-as-google-afb11dff8562

검색 엔진과 fetch as google μžμ²΄κ°€ 같은 λ°©μ‹μœΌλ‘œ μž‘λ™ν•˜μ§€ μ•Šμ„ κ°€λŠ₯성이 맀우 λ†’μŠ΅λ‹ˆλ‹€. 그렇지 μ•ŠμœΌλ©΄ μ§€κΈˆμ―€ μˆœμœ„κ°€ λ–¨μ–΄μ‘Œμ„ κ²ƒμž…λ‹ˆλ‹€. κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³  이 문제λ₯Ό ν•΄κ²°ν•˜κ³  μ‹ΆμŠ΅λ‹ˆλ‹€. μ›Ήμ‚¬μ΄νŠΈ μ†Œμœ μžκ°€ κΉ¨μ–΄ 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

phantomjsλ₯Ό λ‹€μš΄λ‘œλ“œν–ˆλŠ”λ° Googleκ³Ό fetchκ°€ κ°™λ‹€κ³  κ°€μ •ν•˜κ³  λ Œλ”λ§ν•˜λ €κ³  ν•˜λ©΄ μ—¬λŸ¬ 였λ₯˜κ°€ λ°˜ν™˜λ©λ‹ˆλ‹€.

image

이 λ¬Έμ œλŠ” μ»΄νŒŒμΌλ˜λŠ” λŒ€μƒ λΈŒλΌμš°μ €λ₯Ό λ³€κ²½ν•˜λŠ” κ²ƒμ²˜λŸΌ κ°„λ‹¨ν•˜κ²Œ ν•΄κ²°ν•  수 μžˆμ§€λ§Œ λ‹¨μˆœνžˆ babel/polyfil을 μ„€μΉ˜ν•˜κ³  κ°€μ Έμ˜€κΈ°κ°€ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λˆ„κ΅¬λ“ μ§€ μ—¬κΈ°μ—μ„œ μ˜¬λ°”λ₯Έ λ°©ν–₯으둜 λ‚˜λ₯Ό 가리킬 수 μžˆμŠ΅λ‹ˆκΉŒ?

stale

κ°€μž₯ μœ μš©ν•œ λŒ“κΈ€

λ”°λΌμ„œ λ‚΄ μ œμ•ˆ(그리고 이에 λŒ€ν•œ λ¬Έμ„œμ—μ„œ 문제 ν•΄κ²° κ°€μ΄λ“œκ°€ ν•„μš”ν•  수 있음)은 server.js , client.js , App.js 및 잠재적으둜 Razzle μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ "hello world"와 같은 μƒνƒœλ‘œ 되돌리기 μœ„ν•΄ webpack의 μ—¬λŸ¬ 츑면을 μž¬μ •μ˜ν•©λ‹ˆλ‹€(μžˆλŠ” 경우). λ˜ν•œ Razzle의 HTML ν…œν”Œλ¦Ώμ„ server.js 파일둜 λ‹€μ‹œ λ³΅μ‚¬ν•˜μ—¬ λͺ¨λ“  μŠ€ν¬λ¦½νŠΈκ°€ μƒμžμ—μ„œ μ œλŒ€λ‘œ λ‘œλ“œλ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ‹€ν–‰λ˜λ©΄ Chrome 개발 도ꡬλ₯Ό μ—΄κ³  "μ„±λŠ₯" νƒ­μœΌλ‘œ μ΄λ™ν•˜μ—¬ "μŠ€ν¬λ¦°μƒ·" ν™•μΈλž€μ„ μ„ νƒν•œ λ‹€μŒ μƒˆλ‘œ κ³ μΉ¨ μ•„μ΄μ½˜μ„ λˆ„λ¦…λ‹ˆλ‹€. μ•„λž˜ GIFλ₯Ό μ°Έμ‘°ν•˜μ„Έμš”.

kapture 2019-02-05 at 8 33 42

λͺ©ν‘œλŠ” νƒ€μž„λΌμΈ μ˜μ—­μ— 빈 흰색 ν”„λ ˆμž„μ΄ μ—†λŠ”μ§€ ν™•μΈν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. ν”Œλž˜μ‹œ 없이 μ μ ˆν•œ λ Œλ”λ§μ„ 얻은 ν›„μ—λŠ” 이전 μ½”λ“œλ₯Ό 점점 더 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€. 걸음마λ₯Ό λ–Όμ„Έμš”. 이것은 μ£Όμ˜ν•˜μ§€ μ•ŠμœΌλ©΄ 엉망이 되기 μ‰½μŠ΅λ‹ˆλ‹€.

κ·€ν•˜μ˜ νŠΉμ • 사둀에 κ΄€ν•΄μ„œλŠ” λͺ¨λ“  Google νƒœκ·Έ κ΄€λ¦¬μž ν•­λͺ©, λͺ¨λ“  CSS 및 μˆ˜ν–‰ 쀑인 μ½”λ“œ 뢄할을 μ œκ±°ν•˜λŠ” κ²ƒμœΌλ‘œ μ‹œμž‘ν•˜κ² μŠ΅λ‹ˆλ‹€. λ Œλ”λ§ν•˜κ³  거꾸둜 μž‘μ—…ν•˜κΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€. λ‹€μŒμœΌλ‘œ Reduxλ₯Ό μž‘λ™μ‹œν‚€μ‹­μ‹œμ˜€. 그런 λ‹€μŒ react-helmet을 λ‹€μ‹œ μΆ”κ°€ν•˜κ³ , μ œλŒ€λ‘œ λ˜μ—ˆλ‹€κ³  μƒκ°λ˜λ©΄ Lighthouse ν…ŒμŠ€νŠΈλ„ μˆ˜ν–‰ν•˜μ—¬ λ‹€μ‹œ ν™•μΈν•©λ‹ˆλ‹€(감사 νƒ­). λ‚˜μ€‘μ— λ‹€μ‹œ λŒμ•„κ°ˆ 수 μžˆλ„λ‘ 이 μƒνƒœλ₯Ό μƒˆ 뢄기에 μ»€λ°‹ν•©λ‹ˆλ‹€. 그런 λ‹€μŒ 뢄석 슀크립트λ₯Ό λ‹€μ‹œ μΆ”κ°€ν•˜κ³  λ Œλ”λ§μ„ λ‹€μ‹œ ν™•μΈν•©λ‹ˆλ‹€. λ§ˆμ§€λ§‰μœΌλ‘œ μ½”λ“œ 뢄할을 λ‹€μ‹œ μΆ”κ°€ν•΄ λ³΄μ‹­μ‹œμ˜€.

μ°Έκ³ : CSSλ₯Ό μœ μ§€ν•˜κ³  개발 λͺ¨λ“œμ—μ„œ Razzle둜 μœ„μ˜ 뢄석을 μˆ˜ν–‰ν•˜λ©΄ Razzle이 개발 쀑에 μ„œλ²„μ—μ„œ .css μŠ€νƒ€μΌμ„ μ²˜λ¦¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— FOUCλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€(ν΄λΌμ΄μ–ΈνŠΈμ—μ„œλ§Œ). λ”°λΌμ„œ .css νŒŒμΌμ„ μ‚¬μš©ν•˜λŠ” 경우 μŠ€νƒ€μΌ μˆ˜ν™”λ‘œ μ μ ˆν•œ SSR을 μˆ˜ν–‰ν•˜κ³  μžˆλŠ”μ§€ μ™„μ „νžˆ ν…ŒμŠ€νŠΈν•˜λ €λ©΄ ν”„λ‘œλ•μ…˜μš© Razzle을 λΉŒλ“œν•˜κ³  ν”„λ‘œλ•μ…˜ μ„œλ²„λ₯Ό λ‘œμ»¬μ—μ„œ μ‹€ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λ‹¨μˆœνžˆ μ„œλ²„μ—μ„œ HTML이 μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” 경우 개발 λͺ¨λ“œμ—μ„œ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ“  9 λŒ“κΈ€

νŽ˜μ΄μ§€κ°€ 곡백으둜 ν‘œμ‹œλ˜λ©΄ SSR λ˜λŠ” μˆ˜ν™” κ³„νšμ„ μœ„λ°˜ν•œ κ²ƒμž…λ‹ˆλ‹€. Razzle은 기본적으둜 μ„€λͺ…λœ λ‚΄μš©μœΌλ‘œ 인해 어렀움을 κ²ͺ지 μ•ŠμœΌλ©° μ‹€μ œλ‘œ BBC.comκ³Ό 같은 SEO 쀑심 μ‚¬μ΄νŠΈμ—μ„œ μ‚¬μš©λ©λ‹ˆλ‹€. server.js νŒŒμΌμ„ 뢙여넣을 수 μžˆμŠ΅λ‹ˆκΉŒ? μ„œλ²„μ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜€κ³  μžˆμŠ΅λ‹ˆκΉŒ?

μ•ˆλ…•ν•˜μ„Έμš” Jared,

μ‹ μ†ν•œ 닡변에 κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€. 예, μ„œλ²„μ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€.

μ—¬κΈ° λ‚΄ server.jsκ°€ μžˆμŠ΅λ‹ˆλ‹€.

import App from './App';
import React from 'react';
import Helmet from 'react-helmet';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import express from 'express';
import compression from 'compression';
import { renderToString } from 'react-dom/server';

import configureStore from './store/configureStore';
import { remoteLoader } from './api/remoteLoader';
import serialize from 'serialize-javascript';

import { Capture } from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';
import stats from '../build/react-loadable.json';

import { IS_PRODUCTION } from './components/shared/constants';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const server = express();
server.use(compression());

if (!IS_PRODUCTION) {
    server.set('cache', false);
}

server
    .disable('x-powered-by')
    .use(express.static(process.env.RAZZLE_PUBLIC_DIR, { Expires: '30d' }))
    .get('/*', (req, res) => {
        //Ensures clients with old css paths are served the current file
        if (req.path.indexOf('/static/css') > -1 && assets.client.css) {
            const currentCssFile = `${process.env.RAZZLE_PUBLIC_DIR}${assets.client.css}`;
            return res.sendFile(currentCssFile);
        }

        remoteLoader(apiResult => {
            const responseCode = typeof apiResult.status === 'undefined' ? 404 : apiResult.status;

            if (responseCode === 301) {
                return res.redirect(responseCode, apiResult.headers.location);
            }

            // Compile an initial state
            const initialState = {
                remote: {
                    cms: {
                        result: apiResult ? apiResult.data : false,
                        loading: false
                    },
                    myDrewberry: {
                        searchResults: null,
                        loading: false,
                        failed: false
                    }
                }
            };
            // Create a new Redux store instance
            const store = configureStore(initialState);

            const context = {};
            const modules = [];
            const markup = renderToString(
                <Capture report={moduleName => modules.push(moduleName)}>
                    <StaticRouter context={context} location={req.url}>
                        <Provider store={store}>
                            <App />
                        </Provider>
                    </StaticRouter>
                </Capture>
            );
            const helmet = Helmet.renderStatic();

            if (context.url) {
                res.redirect(context.url);
            } else {
                const bundles = getBundles(stats, modules);
                const chunks = bundles.filter(bundle => bundle.file.endsWith('.js'));

                res.status(responseCode).send(
                    `<!doctype html>
                    <html ${helmet.htmlAttributes.toString()}>
                    <head>

                        ${assets.client.css ? `<link rel="stylesheet" href="${assets.client.css}">` : ''}
                        ${helmet.title.toString()}
                        ${helmet.meta.toString()}
                        ${helmet.link.toString()}
                        <link rel="icon" type="image/png" href="/favicon16.png" sizes="16x16"/>
                        <link rel="icon" type="image/png" href="/favicon32.png" sizes="32x32"/>
                        <link rel="icon" type="image/png" href="/favicon96.png" sizes="96x96"/>
                        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/> <!--320-->
                        <meta http-equiv="expires" content="0">
                    </head>
                    <body class="drewberry-preload">

                        <!-- Google Tag Manager -->
                            <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
                            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
                            j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
                            'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
                            })(window,document,'script','dataLayer','GTM-TKPXWB');</script>
                        <!-- End Google Tag Manager -->

                        <div id="root">${markup}</div>
                        <script>
                            window.__PRELOADED_STATE__ = ${serialize(initialState)}
                        </script>
                        ${
                            IS_PRODUCTION
                                ? `<script src="${assets.client.js}"></script>`
                                : `<script src="${assets.client.js}" crossorigin></script>`
                        }
                          ${chunks
                              .map(chunk =>
                                  IS_PRODUCTION
                                      ? `<script src="/${chunk.file}"></script>`
                                      : `<script src="http://${process.env.HOST}:${parseInt(process.env.PORT, 10) + 1}/${
                                            chunk.file
                                        }"></script>`
                              )
                              .join('\n')}
                          <script>window.main();</script>
                    </body>
                </html>`
                );
            }
        }, req.path);
    });

export default server;

λ‚˜λŠ” μ¦‰μ‹œ Razzle이 그것을 κ²ͺ지 μ•Šκ³  μƒˆλ‘œ μ„€μΉ˜λ₯Ό λ‘œλ“œν–ˆκ³  phantomjsλ₯Ό μ‚¬μš©ν•˜μ—¬ κ²½κ³ κ°€ ν‘œμ‹œλ˜μ§€ μ•Šμ•˜μŒμ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μ•„λ§ˆλ„ μš°λ¦¬κ°€ μ„€μΉ˜ν•œ 라이브러리일 수 μžˆμŒμ„ μ˜λ―Έν•©λ‹ˆλ‹€.

μœ„μ˜ λ‚΄μš©μ—μ„œ λΆ„λͺ…ν•œ 것이 λ³΄μ΄μ‹­λ‹ˆκΉŒ?

μˆœκ°„ 기러기 μ«“λŠ” κΈ°λΆ„! :-)

λ”°λΌμ„œ λ‚΄ μ œμ•ˆ(그리고 이에 λŒ€ν•œ λ¬Έμ„œμ—μ„œ 문제 ν•΄κ²° κ°€μ΄λ“œκ°€ ν•„μš”ν•  수 있음)은 server.js , client.js , App.js 및 잠재적으둜 Razzle μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ "hello world"와 같은 μƒνƒœλ‘œ 되돌리기 μœ„ν•΄ webpack의 μ—¬λŸ¬ 츑면을 μž¬μ •μ˜ν•©λ‹ˆλ‹€(μžˆλŠ” 경우). λ˜ν•œ Razzle의 HTML ν…œν”Œλ¦Ώμ„ server.js 파일둜 λ‹€μ‹œ λ³΅μ‚¬ν•˜μ—¬ λͺ¨λ“  μŠ€ν¬λ¦½νŠΈκ°€ μƒμžμ—μ„œ μ œλŒ€λ‘œ λ‘œλ“œλ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ‹€ν–‰λ˜λ©΄ Chrome 개발 도ꡬλ₯Ό μ—΄κ³  "μ„±λŠ₯" νƒ­μœΌλ‘œ μ΄λ™ν•˜μ—¬ "μŠ€ν¬λ¦°μƒ·" ν™•μΈλž€μ„ μ„ νƒν•œ λ‹€μŒ μƒˆλ‘œ κ³ μΉ¨ μ•„μ΄μ½˜μ„ λˆ„λ¦…λ‹ˆλ‹€. μ•„λž˜ GIFλ₯Ό μ°Έμ‘°ν•˜μ„Έμš”.

kapture 2019-02-05 at 8 33 42

λͺ©ν‘œλŠ” νƒ€μž„λΌμΈ μ˜μ—­μ— 빈 흰색 ν”„λ ˆμž„μ΄ μ—†λŠ”μ§€ ν™•μΈν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. ν”Œλž˜μ‹œ 없이 μ μ ˆν•œ λ Œλ”λ§μ„ 얻은 ν›„μ—λŠ” 이전 μ½”λ“œλ₯Ό 점점 더 μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€. 걸음마λ₯Ό λ–Όμ„Έμš”. 이것은 μ£Όμ˜ν•˜μ§€ μ•ŠμœΌλ©΄ 엉망이 되기 μ‰½μŠ΅λ‹ˆλ‹€.

κ·€ν•˜μ˜ νŠΉμ • 사둀에 κ΄€ν•΄μ„œλŠ” λͺ¨λ“  Google νƒœκ·Έ κ΄€λ¦¬μž ν•­λͺ©, λͺ¨λ“  CSS 및 μˆ˜ν–‰ 쀑인 μ½”λ“œ 뢄할을 μ œκ±°ν•˜λŠ” κ²ƒμœΌλ‘œ μ‹œμž‘ν•˜κ² μŠ΅λ‹ˆλ‹€. λ Œλ”λ§ν•˜κ³  거꾸둜 μž‘μ—…ν•˜κΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€. λ‹€μŒμœΌλ‘œ Reduxλ₯Ό μž‘λ™μ‹œν‚€μ‹­μ‹œμ˜€. 그런 λ‹€μŒ react-helmet을 λ‹€μ‹œ μΆ”κ°€ν•˜κ³ , μ œλŒ€λ‘œ λ˜μ—ˆλ‹€κ³  μƒκ°λ˜λ©΄ Lighthouse ν…ŒμŠ€νŠΈλ„ μˆ˜ν–‰ν•˜μ—¬ λ‹€μ‹œ ν™•μΈν•©λ‹ˆλ‹€(감사 νƒ­). λ‚˜μ€‘μ— λ‹€μ‹œ λŒμ•„κ°ˆ 수 μžˆλ„λ‘ 이 μƒνƒœλ₯Ό μƒˆ 뢄기에 μ»€λ°‹ν•©λ‹ˆλ‹€. 그런 λ‹€μŒ 뢄석 슀크립트λ₯Ό λ‹€μ‹œ μΆ”κ°€ν•˜κ³  λ Œλ”λ§μ„ λ‹€μ‹œ ν™•μΈν•©λ‹ˆλ‹€. λ§ˆμ§€λ§‰μœΌλ‘œ μ½”λ“œ 뢄할을 λ‹€μ‹œ μΆ”κ°€ν•΄ λ³΄μ‹­μ‹œμ˜€.

μ°Έκ³ : CSSλ₯Ό μœ μ§€ν•˜κ³  개발 λͺ¨λ“œμ—μ„œ Razzle둜 μœ„μ˜ 뢄석을 μˆ˜ν–‰ν•˜λ©΄ Razzle이 개발 쀑에 μ„œλ²„μ—μ„œ .css μŠ€νƒ€μΌμ„ μ²˜λ¦¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— FOUCλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€(ν΄λΌμ΄μ–ΈνŠΈμ—μ„œλ§Œ). λ”°λΌμ„œ .css νŒŒμΌμ„ μ‚¬μš©ν•˜λŠ” 경우 μŠ€νƒ€μΌ μˆ˜ν™”λ‘œ μ μ ˆν•œ SSR을 μˆ˜ν–‰ν•˜κ³  μžˆλŠ”μ§€ μ™„μ „νžˆ ν…ŒμŠ€νŠΈν•˜λ €λ©΄ ν”„λ‘œλ•μ…˜μš© Razzle을 λΉŒλ“œν•˜κ³  ν”„λ‘œλ•μ…˜ μ„œλ²„λ₯Ό λ‘œμ»¬μ—μ„œ μ‹€ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λ‹¨μˆœνžˆ μ„œλ²„μ—μ„œ HTML이 μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” 경우 개발 λͺ¨λ“œμ—μ„œ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ’‹μ•„μš”, Razzleκ³Ό ν•¨κ»˜ν•œ 멋진 μž‘μ—…μ— κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€. 이제 이에 λŒ€ν•΄ κ°μ‚¬ν•©λ‹ˆλ‹€. κ°μ‚¬νžˆ 잘 보고 λ‹€μ‹œ μ˜¬λ¦¬κ² μŠ΅λ‹ˆλ‹€.

λ¬Όλ‘  "λΉ„μ–΄ μžˆλŠ”"(곡백이 μ•„λ‹ˆλΌ 였λ₯˜μž„) 검색엔진 μ΅œμ ν™” νŽ˜μ΄μ§€μ—μ„œ κ°€μž₯ λ¨Όμ € 확인해야 ν•  사항은 νŽ˜μ΄μ§€κ°€ ν‘œμ‹œν•  데이터λ₯Ό λ‘œλ“œν•˜κΈ° μœ„ν•΄ componentDidMount 에 μ˜μ‘΄ν•©λ‹ˆκΉŒ? curl λ₯Ό μ‚¬μš©ν•˜μ—¬ λΉ λ₯΄κ²Œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. κ·Έλ ‡λ‹€λ©΄ 사전 λ‘œλ“œν•˜λ €λ©΄ After.js와 같은 것이 ν•„μš”ν•©λ‹ˆλ‹€.

1000ms ν‘œμ‹œ μ£Όμœ„μ— 빈 νŽ˜μ΄μ§€κ°€ μžˆλŠ” 경우 μ„±λŠ₯ 탭을 μ‚¬μš©ν•˜μ—¬ λ””λ²„κΉ…ν•˜λŠ” 방법을 μ‘°μ–Έν•΄ μ£Όμ‹œκ² μŠ΅λ‹ˆκΉŒ? λ‚΄ λ¬Έμ œκ°€ μ„œλ²„μ— λ‘œλ“œλ˜μ§€ μ•ŠλŠ” μ•„μ΄μ½˜ κΈ€κΌ΄ λΌμ΄λΈŒλŸ¬λ¦¬μ™€ κ΄€λ ¨λ˜μ–΄ μžˆμœΌλ―€λ‘œ 초기 λ‘œλ“œ μ‹œ μ΄λŸ¬ν•œ 빈 빈 μ‚¬κ°ν˜•μ΄ ν‘œμ‹œλœλ‹€λŠ” 것을 μ•Œκ³  μžˆμŠ΅λ‹ˆλ‹€...

screen shot 2019-02-11 at 9 57 30 am

μ•„, λ‚΄ λͺ¨λ“  μ‚¬μš©μž μ •μ˜ 글꼴은 훨씬 λ‚˜μ€‘μ—κΉŒμ§€ λ‘œλ“œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. CSS의 @font-face {}λ₯Ό 톡해 글꼴을 λ‘œλ“œν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

@font-face {
  font-family: 'SFProText';
  font-display: auto;
  src: url('fonts/SFPro/SF-Pro-Display-Regular.otf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

@jaredpalmer λ‚˜λ„ 같은 λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. κΈ€κΌ΄ 및 μžμ‚°μ΄ λ‚΄ ν΄λΌμ΄μ–ΈνŠΈ νŽ˜μ΄μ§€μ— λ‘œλ“œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λ‚΄ μ‘μš© ν”„λ‘œκ·Έλž¨ 섀정에 webpack.config νŒŒμΌμ„ μΆ”κ°€ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 이 νŒŒμΌμ€ κ·€ν•˜μ˜ λ¦¬ν¬μ§€ν† λ¦¬μ—μ„œ λ³΅μ œν–ˆμŠ΅λ‹ˆλ‹€. 방금 μ €μž₯μ†Œμ—μ„œ 볡사본을 κ°€μ Έ μ™€μ„œ npm λͺ¨λ“ˆμ„ μ„€μΉ˜ν•œ λ‹€μŒ npm start λͺ…령을 μ‹€ν–‰ν•˜μ—¬ μ‘μš© ν”„λ‘œκ·Έλž¨μ„ μ‚¬μš©ν•˜κΈ° μ‹œμž‘ν–ˆμŠ΅λ‹ˆλ‹€.
이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ κ΅¬μ„±ν•˜κΈ° μœ„ν•΄ λ†“μΉœ 것이 μžˆκ±°λ‚˜ μƒˆ μ›ΉνŒ© νŒŒμΌμ„ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆκΉŒ?

이 문제λ₯Ό ν•΄κ²°ν•˜λ„λ‘ λ„μ™€μ£Όμ„Έμš”.

λ‚΄ App.css νŒŒμΌμ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
@κΈ€κΌ΄ μ–Όκ΅΄ {
font-family: 'Roboto-medium';
src: url("./static/fonts/Roboto-Medium-webfont.eot");
src: url("./static/fonts/Roboto-Medium-webfont.eot?#iefix") format("embedded-opentype"), url("./static/fonts/Roboto-Medium-webfont.woff") format("woff"), url("./static/fonts/Roboto-Medium-webfont.ttf") format("truetype"); }

@κΈ€κΌ΄ μ–Όκ΅΄ {
font-family: 'Roboto-regular';
src: url("./static/fonts/robot-regular-webfont.eot");
src: url("./static/fonts/roboto-regular-webfont.eot?#iefix") format("embedded-opentype"), url("./static/fonts/roboto-regular-webfont.woff") format("woff"), url("./static/fonts/roboto-regular-webfont.ttf") format("truetype"); }

κ°μ‚¬ν•©λ‹ˆλ‹€ inadvane :)

이 νŽ˜μ΄μ§€κ°€ 도움이 λ˜μ—ˆλ‚˜μš”?
0 / 5 - 0 λ“±κΈ‰

κ΄€λ ¨ 문제

krazyjakee picture krazyjakee  Β·  3μ½”λ©˜νŠΈ

panbanda picture panbanda  Β·  5μ½”λ©˜νŠΈ

JacopKane picture JacopKane  Β·  3μ½”λ©˜νŠΈ

howardya picture howardya  Β·  5μ½”λ©˜νŠΈ

pseudo-su picture pseudo-su  Β·  3μ½”λ©˜νŠΈ