Поцарапайте их. Правда, их есть на выбор. Но очень немногие из них действительно хорошо работают на мобильных устройствах, не говоря уже о возможности настройки таким образом, чтобы удовлетворить все потребности. В такой среде всегда будет большой компромисс.

Я не говорю, что эта установка для всех. Если ваш местный продуктовый магазин приходит с просьбой о веб-сайте, вы, вероятно, продадите ему сайт WordPress (если вы такой человек) или, что более корректно с этической точки зрения, порекомендуете ему просто перейти на страницу в Facebook.

Но тип проектов / веб-интерфейсов, которыми я занимаюсь в основном на данный момент, имеет модель контента, которая настолько далека от обычного подхода к страницам / категориям / тегам / публикациям, что я принял Contentful и с тех пор очень им доволен. . А с современными интерфейсными фреймворками HTML, такими как Bootstrap, так же легко создавать собственные шаблоны с нуля, и вы получаете полную гибкость вместо нескольких ловушек, которые позволяет вам дизайнер темы.

Давай запачкаем руки.

Отказ от ответственности: остальная часть статьи даже более самоуверенная, чем начало :)

Итак, я подумал, какая установка будет иметь смысл, и в конце концов придумал изоморфный javascript. Когда я говорю это, я обычно провоцирую один из двух типов реакции:

  • насмехаясь, или
  • аплодисменты.

Честно говоря, я к технологиям большую часть времени относился равнодушно. Я взвешиваю плюсы и минусы и выбираю то, что лучше всего подходит для моего бизнеса. Правда, на этом пути я редко достигаю истинной виртуозности, но и обратное тоже опасно: если все, что вы знаете, - это молоток, все начинает походить на гвоздь.

Поскольку у меня был некоторый опыт программирования на React (и учетная запись now.sh), я выбрал next.js в качестве фреймворка. Что мне вообще нравится в проектах Zeit, так это их минималистичный подход ко всему, то же самое и с next.js. По сравнению с create-react-app вариант использования довольно ограничен: серверные сайты на React. Я бы даже не стал заходить так далеко и утверждать, что приложения являются их основной целью. Нет необходимости в маршрутизаторах и контейнерах состояний из коробки, вы создаете навигацию по сайту с помощью тегов <Link> и имеете волшебный каталог /pages, который будет смонтирован в корне вашего приложения. Каждый добавленный вами компонент React будет соответствовать маршруту, поэтому /pages/event.js будет доступен через /event/ и т. Д.

Также есть папка /static, в которой будут храниться ваши активы, но это все. Очень самоуверенный, но вы понимаете

  • автоматическое разделение кода,
  • дополнительные методы жизненного цикла для выборки данных на стороне сервера
  • предварительная загрузка страницы
  • динамический импорт
  • и более…

Опять же, плотный каркас, который затем возложит на ваше приложение, подойдет лишь для небольшого процента случаев использования. Но для этого и для меня он почти идеально подошел. И частично, я должен признать, это связано с тем, что с next нет необходимости отделять клиентский код от серверного. Меньше возможных проблем, меньше шаблонного кода, больше скорость разработки, что означает для меня.

Установка плиты для кипячения

После успешного запуска подобного проекта я иногда делаю шаг назад и сводлю все к шаблону (отсюда и название публикации). Я тоже что-то записываю, чтобы не забыть. И, как мне указали некоторые коллеги, другие тоже могут счесть это полезным. Так что да, это еще один блог на TIL… прошу прощения за мою посредственность :)

Базовый next.js

Итак, давайте рассмотрим этот фрагмент по отдельности. Начнем с наших зависимостей и рассмотрим наш package.json. Соответствующие части для установки next.js:

{
    "scripts": {
        "dev": "node server.js",
        "build": "next build",
        "start": "NODE_ENV=production node server.js"
    },
    ...
    "dependencies": {
        "express": "^4.16.3",
        ...
        "next": "^5.1.0",
        "react": "^16.3.1",
        "react-dom": "^16.3.1",
    }
}

Зачем здесь нужен экспресс, мы рассмотрим позже, но три основных пакета - это next, react и react-dom, вот и все. Давайте пока просто добавим страницу индекса и поместим ее в /pages:

const Index = () => (
  <div style={{width: 640, maxWidth: '100%', margin: 'auto'}}>
    <h1>Hello from Next</h1>
    <img src="static/boiling_plates.svg" />
  </div>
);

export default Index;

Давайте установим пакеты, нажмем npm run dev и посмотрим, что у нас получится (примечание: в этом пошаговом руководстве я использую узел v8.9.4). Перейдите по адресу http: // localhost: 3000 и вы должны получить:

Привет! Похоже, мы закончили. Нет, серьезно. Нам нужен крутой, отзывчивый внешний вид, верно? Давайте начнем.

Бутстрап 4

Это требует небольшой настройки. Во-первых, несколько дополнительных записей в нашем package.json:

{   
    ...
    "dependencies": {
        "autoprefixer": "^8.2.0",
        "bootstrap": "^4.0.0",
        "jquery": "^3.3.1",
        "node-sass": "^4.8.3",
        "popper.js": "^1.14.3",
        "postcss-easy-import": "^3.0.0",
        "postcss-loader": "^2.1.3",
        "raw-loader": "^0.5.1",
        "sass-loader": "^6.0.7"
    }
}

Давайте посмотрим. jQuery и popper.js требуются для Bootstrap, так что все. В next.js.config, который является разновидностью webpack.config.js следующего вкуса, мы настраиваем загрузку файлов SCSS следующим образом:

...
{
    test: /\.s(a|c)ss$/,
    use: ['raw-loader', 'postcss-loader',
      { loader: 'sass-loader',
        options: {
          includePaths: ['styles', 'node_modules']
            .map((d) => path.join(__dirname, d))
            .map((g) => glob.sync(g))
            .reduce((a, c) => a.concat(c), [])
        }
      }
    ]
}
...

Это объясняет использование raw-loader и postcss-loader: файлы SCSS загружаются и затем обрабатываются postcss. Итак, давайте теперь посмотрим на эту конфигурацию:

module.exports = {
    plugins: [
          require('postcss-easy-import')({prefix: '_'}),      
          require('autoprefixer')({ /* ...options */ })
    ]
  }

(postcss.config.js)

Мы используем его для автопрефиксации наших таблиц стилей и также для импорта (отсюда и префикс «_»: загрузка импортирует частичный стиль).

После этого sass-loader сотворит чудеса с нашим SCSS и объединит его.

Теперь для части Bootstrap нам также нужно вставить ProvidePlugin в соответствии с документацией:

config.plugins.push(
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    'window.jQuery': 'jquery',
    Popper: ['popper.js', 'default'],
   }),
);

Итак, как нам добавить таблицу стилей на нашу страницу? Вот как это выглядит (index.scss), довольно неинтересно :)

@import "~bootstrap/scss/bootstrap";

Во-первых, давайте создадим папку с именем /components (на самом деле может называться как угодно) и создадим layout.js компонент.

Похоже, что предпочтительный способ загрузки CSS в Next - более или менее скопировать и вставить его в раздел <Head>, css-in-js-style:

import stylesheet from '../styles/index.scss';
// ...
<Head>
  <title>Next / Contentful / Bootstrap 4 Boilerplate</title>
  <meta charSet="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <style dangerouslySetInnerHTML={{ __html: stylesheet }} />      
</Head>

Здесь нет минификации. Я почти уверен, что есть способы оптимизировать это, но пока я оставлю все как есть.

Чтобы завершить настройку Bootstrap, нам также понадобится javascript, да? Это было сложнее всего понять. Из-за рендеринга на стороне сервера нам нужно поместить инициализацию в ловушку жизненного цикла componentDidMount React:

componentDidMount() {
    window.$ = window.jQuery = require('jquery');
    require('bootstrap');
  }

Эта часть выполняется только тогда, когда разметка готова, на стороне клиента. Итак, давайте добавим несколько служебных классов text и bootstrap, обернем страницу Index в наш Layout и обновим сервер разработки:

<Layout>
    <div className="text-center">
      <h1>Hello from Next</h1>
      <p className="lead">Lorem ipsum... yeah whatever...</p>
      <img className="img-fluid w-75 mt-4" src="static/boiling_plates.svg" />
    </div>
  </Layout>

Вот что мы получаем, Bootstrap вроде работает нормально:

Делаем это содержательным

Я не собираюсь описывать здесь весь контентный интерфейс и процедуру настройки, пожалуйста, обратитесь к документации :)

Вместо этого я расскажу, как настроить Contentful client в этой среде. Сначала npm i --save contentful и создайте .env файл со своими учетными данными и ID пространства:

CTF_SPACE_ID=<your space id>
CTF_CDA_TOKEN=<your CDA token>
CTF_CPA_TOKEN=<your CPA token>

Для этого нам понадобится dotenv модуль npm: npm i --save dotenv и загрузим его в next.config.js:

require('dotenv').config();

Это сделает наши переменные среды доступными через process.env. Теперь также загрузите EnvironmentPlugin в тот же файл:

config.plugins.push(
  new webpack.EnvironmentPlugin(process.env),
    //...
    )

Это сделает эти переменные доступными во время компиляции! Теперь мы можем ссылаться на них в нашем коде, например. в файле common/contentful.js:

const contentful = require('contentful');

const defaultConfig = {
  CTF_SPACE_ID: process.env.CTF_SPACE_ID,
  CTF_CDA_TOKEN: process.env.CTF_CDA_TOKEN,
  CTF_CPA_TOKEN: process.env.CTF_CPA_TOKEN
};

module.exports = {
  createClient (config = defaultConfig) {
    const options = {
      host: 'preview.contentful.com',
      space: config.CTF_SPACE_ID,
      accessToken: config.CTF_CPA_TOKEN
    };    

    if(process.env.NODE_ENV === 'production' && !process.env.STAGING) {
      options.host = 'cdn.contentful.com';
      options.accessToken = config.CTF_CDA_TOKEN;
    }

    return contentful.createClient(options);
  }
};

Цель этого файла - предоставить фабричный метод, который настроит ваш Contentful-клиент в зависимости от того, находитесь ли вы в среде разработки или в производственной среде, или, в нашем случае, в пользовательской среде STAGING, что я считаю полезным. Здесь мы выбираем, используем ли мы Delivery API (рабочая версия) или Preview API (разработка, постановка), которые также будут включать неопубликованные записи.

Давайте теперь представим, что мы уже собираемся загрузить некоторые контентные данные на нашу страницу индекса. Нам нужно импортировать только что созданный метод factory:

import { createClient } from '../common/contentful';

//...

Index.getInitialProps = async () => {
  const client = createClient();

  const entries = await client.getEntries({
    // some query
  });
  
  return { someEntryAsProp: entries.items[0] };
};

Позже в этом классе мы вызываем getInitialProps, это метод жизненного цикла, который nextjs добавляет в наш класс React. Здесь мы можем использовать асинхронный код и просто получить некоторые записи из Contentful и передать их через props. В нашем методе render это будет доступно как опора:

//...

<Layout>
    <div className="text-center">    
      <h1>Hello from Next</h1>
      <p>{someEntryAsProp.fields.text}</p> // <-- HERE
      <img className="img-fluid w-75 mt-4" src="static/boiling_plates.svg" />
    </div>
</Layout>
  
//...

Вот и все. Теперь мы можем выполнять произвольные запросы в бэкэнде Contentful и получать наш контент либо на стороне сервера через getInitialProps, либо на стороне клиента, если мы делаем это в componentDidMount.

Server.js

А теперь небольшое примечание о том, что Express server.js прячется в нашем корневом каталоге. Подробное объяснение nextjs также выходит за рамки этой статьи (она и без того довольно длинная :)) - если интересно, загляните сюда. Оказывается, следующий очень умен, но недостаточно умен, чтобы угадать маскировку вашего маршрута. Что я имею в виду?

Далее вы можете объявить такую ​​ссылку:

<Link as={`/a/${props.id}`} href={`/article?id=${props.id}`}>
    <a>{props.title}</a>
</Link>

И это сработает (даст вам чистые URL-адреса типа / a / my-awesome-article), пока навигация осуществляется на стороне клиента, потому что next творит чудеса с историей вашего браузера (pushState и т. Д.).

Однако, если вы обновите окно браузера после перехода по этому маршруту, это приведет к ошибке 404. Итак, в server.js есть этот маршрут, перевод сокращенного URL-адреса на правильную страницу в каталоге / pages и добавьте идентификатор в строку запроса.

server.get('/a/:id', (req, res) => {
  const actualPage = '/article';
  const queryParams = { id: req.params.id };
  app.render(req, res, actualPage, queryParams);
});

Развернуть сейчас

Эта часть самая легкая из всех. Потому что и следующий, и сейчас - проекты Zeit, так и должно быть, не так ли?

Оказывается, now развернет все, что имеет сценарии npm build и npm start. Единственное, о чем мы должны позаботиться, - это секреты нашего проекта. Сейчас с этим довольно легко справиться. Объявляем их в нашей консоли:

> now secrets add ctf_space_id "my_value"
> now secrets add ctf_cda_token "my_value"
> now secrets add ctf_cpa_token "my_value"

И ссылайтесь на них в нашем now.json:

{
  "env": {
    "CTF_SPACE_ID": "@ctf_space_id",
    "CTF_CDA_TOKEN": "@ctf_cda_token",
    "CTF_CPA_TOKEN": "@ctf_cpa_token"
  }
}

Вот и все. Выполните развертывание в нашей тестовой среде, дополнительно указав переменную среды STAGING:

> now -e STAGING=1

и, наконец, к производству:

> now

Найдите настоящий шаблон здесь.