Full Code of wantainc/microcms-blog for AI

production d42e5246b4c3 cached
56 files
186.2 KB
54.8k tokens
9 symbols
1 requests
Download .txt
Showing preview only (205K chars total). Download the full file or copy to clipboard to get everything.
Repository: wantainc/microcms-blog
Branch: production
Commit: d42e5246b4c3
Files: 56
Total size: 186.2 KB

Directory structure:
gitextract__39a8pyr/

├── .eslintrc.js
├── .gitignore
├── .node-version
├── .prettierrc
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── LICENSE
├── README.md
├── assets/
│   ├── README.md
│   └── styles/
│       ├── colors.css
│       └── reset.css
├── components/
│   ├── Alert.vue
│   ├── Banners.vue
│   ├── Breadcrumb.vue
│   ├── Card.vue
│   ├── Categories.vue
│   ├── ConversionPoint.vue
│   ├── Cta.vue
│   ├── Footer.vue
│   ├── Header.vue
│   ├── Latest.vue
│   ├── Logo.vue
│   ├── Meta.vue
│   ├── NextBlogNavigation.vue
│   ├── Pagination.vue
│   ├── Partner.vue
│   ├── PopularArticles.vue
│   ├── Post.vue
│   ├── README.md
│   ├── RelatedBlogs.vue
│   ├── Search.vue
│   ├── Share.vue
│   ├── ShareButtons.vue
│   ├── Tags.vue
│   ├── Toc.vue
│   └── Writer.vue
├── functions/
│   ├── draft.js
│   └── search.js
├── layouts/
│   ├── README.md
│   └── default.vue
├── netlify.toml
├── nuxt.config.js
├── package.json
├── pages/
│   ├── 404.vue
│   ├── README.md
│   ├── _slug/
│   │   └── index.vue
│   ├── author/
│   │   └── _authorId.vue
│   ├── draft/
│   │   └── index.vue
│   ├── index.vue
│   └── search/
│       └── index.vue
├── plugins/
│   ├── README.md
│   └── vue-scrollto.js
├── static/
│   ├── README.md
│   └── robots.txt
└── utils/
    ├── getDefaultOgimage.js
    └── microcms.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
  },
  parserOptions: {
    parser: 'babel-eslint',
  },
  extends: [
    '@nuxtjs',
    'prettier',
    'prettier/vue',
    'plugin:prettier/recommended',
    'plugin:nuxt/recommended',
  ],
  plugins: ['prettier'],
  // add your custom rules here
  rules: {
    'vue/no-v-html': 'off',
  },
};


================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# Nuxt generate
dist

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless

# IDE / Editor
.idea
.editorconfig

# Service worker
sw.*

# Mac OSX
.DS_Store

# Vim swap files
*.swp

#amplify
amplify/\#current-cloud-backend
amplify/.config/local-*
amplify/backend/amplify-meta.json
amplify/backend/awscloudformation
build/
dist/
node_modules/
aws-exports.js
awsconfiguration.json

#rss
feed.xml
feed_update.xml
feed_usecase.xml

#pwa
sw.*

================================================
FILE: .node-version
================================================
16.20.0

================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "singleQuote": true
}


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "simonsiefke.prettier-vscode"
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "editor.formatOnSave": true
}

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# microcms-blog
サイト: https://blog.microcms.io

## 機能
- 記事一覧
- カテゴリー別記事一覧
- タグ別記事一覧
- 人気の記事一覧
- 最新の記事一覧
- 著者別の記事一覧
- 検索
- パンくずリスト
- 記事詳細
  - 目次
  - 著者
  - SNSシェアボタン
  - 下書きプレビュー
  - 関連記事
- サイトマップ
- バナー
- Google Tag Manager
- Facebook Pixel
- RSS
- PWA

## 技術構成
- Nuxt(SSG)
- microCMS(コンテンツ)
- Netlify(Hosting, Functions)
- ESLint
- Prettier
- PostCSS
- PWA

## microCMSのAPIスキーマ設定
### ブログ
endpoint: blog  
type: リスト形式

| フィールド ID | 表示名     | 種類                        |
| ------------- | ---------- | --------------------------- |
| title         | タイトル   | テキストフィールド          |
| category      | カテゴリー | コンテンツ参照 - カテゴリー |
| tag           | タグ       | 複数コンテンツ参照 - タグ   |
| toc_visible   | 目次       | 真偽値                      |
| body          | 本文       | リッチエディタ              |
| description   | 概要       | テキストフィールド          |
| ogimage       | OGP 画像   | 画像                        |
| writer        | 著者       | コンテンツ参照 - 著者       |
| partner       | パートナー | コンテンツ参照 - パートナー |
| previous_blog | 前の記事   | コンテンツ参照 - ブログ |
| next_blog     | 次の記事   | コンテンツ参照 - ブログ |
| related_blogs | 関連記事   | 複数コンテンツ参照 - ブログ |
| cv_point | CVポイント   | 繰り返し(※上限1に設定) - カスタムフィールド |

#### カスタムフィールド
フィールドID: thumbnail

| フィールド ID | 表示名     | 種類                        |
| ------------- | ---------- | --------------------------- |
| title         | タイトル   | テキストフィールド          |
| text      | 本文 | テキストエリア |
| buttonText           | ボタンテキスト       | テキストフィールド   |
| buttonLink           | ボタンリンク       | テキストフィールド   |
| thumbnail       | サムネイル   | 画像                        |


### 著者
endpoint: authors  
type: リスト形式

| フィールドID | 表示名 | 種類 |
| ------------- | ------------- | ----- |
| name | 名前 | テキストフィールド |
| text | 自己紹介 | テキストエリア |
| image | 画像 | 画像 |
| twitter | Twitter URL | テキストフィールド |
| facebook | Facebook URL | テキストフィールド |
| github | GitHub URL | テキストフィールド |

### カテゴリー
endpoint: categories  
type: リスト形式

| フィールドID | 表示名 | 種類 |
| ------------- | ------------- | ----- |
| name | 名前 | テキストフィールド |

### タグ
endpoint: tags
type: リスト形式

| フィールド ID | 表示名 | 種類               |
| ------------- | ------ | ------------------ |
| name          | 名前   | テキストフィールド |

### パートナー
endpoint: partners  
type: リスト形式

| フィールドID | 表示名 | 種類 |
| ------------- | ------------- | ----- |
| company | 会社名 | テキストフィールド |
| url | 会社URL | テキストフィールド |
| description | 説明文 | テキストエリア |
| logo | ロゴ | 画像 |

### 人気の記事
endpoint: popular-articles  
type: オブジェクト形式

| フィールドID | 表示名 | 種類 |
| ------------- | ------------- | ----- |
| articles | 人気の記事 | 複数コンテンツ参照 - ブログ |

### バナー
endpoint: banners
type: オブジェクト形式

| フィールドID | 表示名 | 種類 |
| ------------- | ------------- | ----- |
| banner | バナー | 繰り返し - カスタムフィールド |

#### カスタムフィールド
フィールドID: cf_banner

| フィールド ID | 表示名     | 種類                        |
| ------------- | ---------- | --------------------------- |
| image | 画像 | 画像 |
| url | リンク先URL | テキストフィールド |

フィールドID: cf_section_title

| フィールド ID | 表示名     | 種類                        |
| ------------- | ---------- | --------------------------- |
| title | タイトル | テキストフィールド |

### CTA
endpoint: cta  
type: オブジェクト形式

| フィールドID | 表示名 | 種類 |
| ------------- | ------------- | ----- |
| contents | CTAフィールド本文 | 繰り返し - カスタムフィールド |

#### カスタムフィールド
フィールドID: cf_cta

| フィールド ID | 表示名     | 種類                        |
| ------------- | ---------- | --------------------------- |
| body      | 本文 | リッチエディタ |

## 環境変数
プロジェクトルートに`.env`ファイルを作成し、以下の項目を設定してください。
- API_KEY(microCMSのAPIキー)
- SERVICE_ID(microCMSのサービスID)
- GTM_ID(Google Tag ManagerのID)※任意
- FB_PIXEL_ID(FacebookピクセルID)※任意

例:
```
API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SERVICE_ID=your-service-id
GTM_ID=GTM-xxxxxxx
FB_PIXEL_ID=xxxxxxxxxxxxxxxxxx
```

## 開発方法

```bash
# パッケージをインストール
$ npm install

# 開発サーバーを起動(localhost:3000)
$ npm run dev

# Netlify Functionsをローカルで起動(localhost:9000)
$ npm run functions:serve

# アプリケーションを静的生成
$ npm run generate

# 静的生成したアプリケーションを起動
$ npm start
```

## ライセンス
Apache License 2.0


================================================
FILE: assets/README.md
================================================
# ASSETS

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).


================================================
FILE: assets/styles/colors.css
================================================
:root {
  --color-text-main: #2b2c30;
  --color-text-sub: #616269;
  --color-text-off: #999;
  --color-text-disabled: #ccc;
  --color-text-placeholder: #ccc;
  --color-text-link: #2b2c30;
  --color-border-dark: #ccc;
  --color-border: #ddd;
  --color-border-light: #eee;
  --color-primary: #563bff;
  --color-primary-light: #664bff;
  --color-gradient-purple: linear-gradient(to right bottom, #5630af, #3067af);
  --color-gradient-purple-light: linear-gradient(
    to right bottom,
    #7650cf,
    #5087cf
  );
  --color-gradient-blue: linear-gradient(to right bottom, #adaf30, #30af7f);
  --color-purple: #331cbf;
  --color-green: #2cc63e;
  --color-bluegreen: #30af7f;
  --color-gray: #ddd;
  --color-gray-light: #eee;
  --color-accent: #ff8d27;
  --color-accent-light: #ff9d37;
  --color-blue: #3067af;
  --color-pink: #ff357f;

  --color-bg-purple-lightest: #f8f9fd;
  --color-bg-purple-light: #f7f7fc;
  --color-bg-purple: #e7e7f3;
  --color-bg-purple-dark: #cacae7;
  --color-bg-blue: #e5eff9;

  --color-bg-success: #e8fbe8;
  --color-bg-error: #ffe9df;
  --color-error: #d9534f;

  --color-social-twitter: #1d9bf0;
  --color-social-x: #0f1419;
  --color-social-facebook: #1877f2;
  --color-social-hatena: #00a4de;
}


================================================
FILE: assets/styles/reset.css
================================================
/* http://meyerweb.com/eric/tools/css/reset/ 
v2.0 | 20110126
License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}

/* HTML5 display-role reset for older browsers 
*/

article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
display: block;
}

body, input {
  line-height: 1.5;
  font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", YuGothic, "ヒラギノ角ゴ ProN W3", Hiragino Kaku Gothic ProN, Arial, "メイリオ", Meiryo, sans-serif;
  color: #2b2c30;
}

code {
  font-family: Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
}

ol, ul {
list-style: none;
}

blockquote, q {
quotes: none;
}

blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}

a {
color: var(--color-text-link);
text-decoration: none;
}

table {
border-collapse: collapse;
border-spacing: 0;
}

input, select, button {
  &:focus {
    outline: none;
  }
}

:placeholder-shown {
color: var(--color-text-placeholder);
}

::-webkit-input-placeholder {
color: var(--color-text-placeholder);
}

/* Firefox 18- */

:-moz-placeholder {
color: var(--color-text-placeholder);
opacity: 1;
}

/* Firefox 19+ */

::-moz-placeholder {
color: var(--color-text-placeholder);
opacity: 1;
}

/* IE 10+ */

:-ms-input-placeholder {
color: var(--color-text-placeholder);
}

/* for Responsible */

@media (max-width: 1030px) {
  .forPC {
    display: none;
  }
}

@media (min-width: 1030px) {
  .forSP {
    display: none;
  }
}


================================================
FILE: components/Alert.vue
================================================
<template>
  <div v-if="isOldPost && isTechnicalPost" class="alert">
    <p>
      この記事は公開後、1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
    </p>
  </div>
</template>

<script>
export default {
  props: {
    isOldPost: {
      type: Boolean,
      required: true,
    },
    isTechnicalPost: {
      type: Boolean,
      required: true,
    },
  },
};
</script>

<style scoped>
.alert {
  position: relative;
  background-color: #faf6da;
  color: var(--color-text-main);
  margin-bottom: 40px;
  padding: 16px 16px 18px 44px;
  font-size: 14px;
  border-radius: 4px;

  &::before {
    content: '';
    display: inline-block;
    background: url('/images/icon_alert.svg') center no-repeat;
    background-size: contain;
    width: 18px;
    height: 18px;
    position: absolute;
    top: 50%;
    left: 16px;
    transform: translateY(-50%);
  }
}
</style>


================================================
FILE: components/Banners.vue
================================================
<template>
  <div class="wrapper">
    <template v-for="(item, idx) in normalizedBanners">
      <!-- セクションタイトル -->
      <h2
        v-if="item.fieldId === 'cf_section_title' && item.title"
        :key="`title-${idx}`"
        class="sectionTitle"
      >
        {{ item.title }}
      </h2>

      <!-- バナー:リンクあり -->
      <a
        v-else-if="
          item.fieldId === 'cf_banner' &&
          item.image &&
          item.image.url &&
          item.url
        "
        :key="`${item.id || idx}-link`"
        :href="item.url"
        class="link blog-cta-link"
        target="banner"
        rel="noopener"
      >
        <picture>
          <source
            type="image/webp"
            :data-srcset="`${item.image.url}?w=300&fm=webp, ${item.image.url}?w=600&fm=webp 2x`"
          />
          <img
            :data-src="item.image.url"
            :width="item.image.width"
            :height="item.image.height"
            class="image lazyload"
            :alt="item.image.alt || ''"
          />
        </picture>

        <p
          v-if="item.description"
          class="description"
          v-text="item.description"
        ></p>
      </a>

      <!-- バナー:リンクなし -->
      <div
        v-else-if="item.fieldId === 'cf_banner' && item.image && item.image.url"
        :key="`${item.id || idx}-nolink`"
        class="link blog-cta-link"
      >
        <picture>
          <source
            type="image/webp"
            :data-srcset="`${item.image.url}?w=560&fm=webp, ${item.image.url}?w=600&fm=webp 2x`"
          />
          <img
            :data-src="item.image.url"
            :width="item.image.width"
            :height="item.image.height"
            class="image lazyload"
            :alt="item.image.alt || ''"
          />
        </picture>
        <p
          v-if="item.description"
          class="description"
          v-text="item.description"
        ></p>
      </div>
    </template>
  </div>
</template>

<script>
export default {
  props: {
    banners: {
      type: [Array, Object],
      required: false,
      default: () => [],
    },
    id: {
      type: String,
      required: true,
    },
  },
  computed: {
    normalizedBanners() {
      if (Array.isArray(this.banners)) return this.banners;
      if (this.banners && Array.isArray(this.banners.banner)) {
        return this.banners.banner;
      }
      return [];
    },
  },
};
</script>

<style scoped>
.wrapper {
  padding-bottom: 20px;
}
.sectionTitle {
  font-size: 20px;
  font-weight: bold;
  background-color: #eee;
  padding: 6px 10px;
  margin-bottom: 20px;
  border-radius: 5px;
}
.image {
  width: 100%;
  height: auto;
}

.description {
  padding-top: 7px;
  white-space: pre-line;
}

.link {
  display: block;
  margin-bottom: 30px;
}

@media (max-width: 1160px) {
  .link {
    text-align: center;
  }
}
</style>


================================================
FILE: components/Breadcrumb.vue
================================================
<template>
  <ul class="breadcrumb">
    <li class="breadcrumbList">
      <nuxt-link to="/">記事一覧</nuxt-link>
    </li>
    <li v-if="hasCategory(category)" class="breadcrumbList">
      <nuxt-link :to="`/category/${category.id}/page/1`">{{
        category.name
      }}</nuxt-link>
    </li>
    <li v-else-if="hasCategory(tag)" class="breadcrumbList">
      <nuxt-link :to="`/tag/${tag.id}/page/1`">{{ tag.name }}</nuxt-link>
    </li>
  </ul>
</template>

<script>
export default {
  props: {
    category: {
      type: Object,
      required: false,
      default: () => ({}),
    },
    tag: {
      type: Object,
      required: false,
      default: () => ({}),
    },
  },
  methods: {
    hasCategory(arg) {
      return Object.keys(arg).length > 0;
    },
  },
};
</script>

<style scoped>
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  padding-top: 20px;
}

.breadcrumbList {
  color: #616269;

  a {
    color: #331cbf;
  }

  &::after {
    content: '>';
    margin: 0 10px;
  }

  &:last-child&::after {
    content: '';
    margin: 0;
  }
}
@media (max-width: 600px) {
  .breadcrumbList {
    font-size: 14px;
  }
}
</style>


================================================
FILE: components/Card.vue
================================================
<template class="list">
  <nuxt-link :to="`/${content.id}/`" class="link">
    <picture v-if="content.ogimage">
      <source
        type="image/webp"
        :data-srcset="content.ogimage.url + '?w=680&fm=webp'"
      />
      <img
        :data-src="content.ogimage.url + '?w=680'"
        :width="340"
        class="ogimage lazyload"
        alt
      />
    </picture>
    <picture v-else>
      <source type="image/webp" :data-srcset="content.defaultOgimage" />
      <img
        :data-src="content.defaultOgimage"
        :width="340"
        class="ogimage lazyload"
        alt
      />
    </picture>
    <dl class="content">
      <dt class="title">
        {{ content.title }}
      </dt>
      <dd>
        <div class="upper">
          <span class="category">{{ content.category.name }}</span>
        </div>
        <div class="meta">
          <div class="author">
            <img
              :data-src="author.image.url + '?w=80&h=80'"
              :width="40"
              :height="40"
              class="authorimage lazyload"
              alt
            />
            <span>{{ author.name }}</span>
          </div>
          <div class="timestamp">
            <time
              :datetime="
                $dayjs(content.publishedAt || content.createdAt).format(
                  'YYYY-MM-DD'
                )
              "
            >
              {{
                $dayjs(content.publishedAt || content.createdAt).format(
                  'YYYY/MM/DD'
                )
              }}
            </time>
          </div>
        </div>
      </dd>
    </dl>
  </nuxt-link>
</template>

<script>
export default {
  props: {
    content: {
      type: Object,
      required: true,
      default: () => {},
    },
    author: {
      type: Object,
      required: true,
      default: () => {},
    },
  },
};
</script>

<style scoped>
.link {
  display: block;
}
.ogimage {
  width: 340px;
}
.content {
  margin: 16px 0 0;
}
.title {
  font-size: 20px;
  font-weight: bold;
}
.upper {
  margin-top: 16px;
}
.category {
  display: inline-block;
  padding: 8px 16px;
  white-space: nowrap;
  color: var(--color-text-main);
  font-weight: bold;
  border-radius: 4px;
  font-size: 14px;
  margin-right: 16px;
  background: var(--color-gray-light);
}
.meta {
  display: flex;
  align-items: center;
  margin-top: 16px;
}
.author,
.timestamp {
  display: inline-flex;
  align-items: center;
  margin-right: 16px;
  color: var(--color-text-sub);
}
.authorimage {
  border-radius: 100px;
  margin-right: 8px;
}
.author {
  font-weight: bold;
}
@media (max-width: 820px) {
  .ogimage {
    width: 100%;
  }
}
</style>


================================================
FILE: components/Categories.vue
================================================
<template>
  <div class="wrapper">
    <h1 class="pageTitle">カテゴリー</h1>
    <ul>
      <li v-for="category in categories" :key="category.id" class="list">
        <nuxt-link :to="`/category/${category.id}/page/1`" class="link">{{
          category.name
        }}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    categories: {
      type: Array,
      required: false,
      default: () => [],
    },
  },
};
</script>

<style scoped>
@media (min-width: 1160px) {
  .wrapper {
    padding: 40px 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    border-bottom: 1px solid #eee;

    a {
      display: block;
      padding: 10px;
    }

    &:last-child {
      border-bottom: none;
    }
  }
}
@media (min-width: 820px) and (max-width: 1160px) {
  .wrapper {
    padding: 40px 0 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    border-bottom: 1px solid #eee;

    a {
      display: block;
      padding: 10px;
    }

    &:last-child {
      border-bottom: none;
    }
  }
}
@media (max-width: 820px) {
  .wrapper {
    padding: 40px 0 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    border-bottom: 1px solid #eee;

    a {
      display: block;
      padding: 10px;
    }

    &:last-child {
      border-bottom: none;
    }
  }
}
</style>


================================================
FILE: components/ConversionPoint.vue
================================================
<template>
  <div :class="{ [`cvPoint--${theme}`]: theme !== '' }" class="wrapper cvPoint">
    <div v-if="theme === 'thumbnail'" class="upper">
      <div class="upperContents">
        <div>
          <h2 class="mainTitle">{{ getContents.title }}</h2>
          <p class="mainText">{{ getContents.text }}</p>
        </div>
        <figure class="thumbnail">
          <img :src="getContents.thumbnail.url" alt="" />
        </figure>
      </div>
      <p class="buttonWrapper">
        <a
          class="button blog-cta-link"
          target="site"
          :href="getContents.buttonLink"
          >{{ getContents.buttonText }}</a
        >
      </p>
    </div>

    <div v-else>
      <div class="cvBackground">
        <div class="cvContainer">
          <h2 class="mainTitle">{{ getContents.title }}</h2>
          <p class="mainContents">{{ getContents.text }}</p>
          <p class="buttonWrapper">
            <a
              class="button blog-cta-link"
              target="site"
              :href="getContents.buttonLink"
              >{{ getContents.buttonText }}</a
            >
          </p>
        </div>
      </div>

      <div class="bottom">
        <div class="background">
          <h3 class="subTitle">microCMSについてお問い合わせ</h3>
          <p>
            初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。
          </p>
          <a
            href="https://microcms.io/contact"
            class="buttonSmall blog-cta-link"
            >お問い合わせ</a
          >
        </div>

        <div class="background">
          <h3 class="subTitle">microCMS公式アカウント</h3>
          <p>
            microCMSは各公式アカウントで最新情報をお届けしています。<br />フォローよろしくお願いします。
          </p>
          <ul class="iconList">
            <li class="listItem">
              <a target="_blank" href="https://x.com/micro_cms">
                <img src="/images/icon_x.svg" alt="X" data-logo="x" />
              </a>
            </li>
            <li class="listItem">
              <a target="_blank" href="https://discord.gg/K3DPqw4EJ2">
                <img src="/images/icon_discord.svg" alt="Discord" />
              </a>
            </li>
            <li class="listItem">
              <a target="_blank" href="https://github.com/microcmsio">
                <img src="/images/icon_github.svg" alt="github" />
              </a>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    id: {
      type: String,
      required: true,
      default: '',
    },

    contents: {
      type: Array,
      required: false,
      default: () => [],
    },

    theme: {
      type: String,
      required: false,
      default: '',
      validator: (value) => ['', 'thumbnail'].includes(value) !== -1,
    },
  },

  computed: {
    getContents() {
      if (this.contents === null || this.contents.length <= 0) {
        return {
          title: 'まずは、無料で試してみましょう。',
          text:
            'APIベースの日本製ヘッドレスCMS「microCMS」を使えば、\nものの数分でAPIの作成ができます。',
          buttonText: 'microCMSを無料で始める',
          buttonLink: 'https://microcms.io/',
        };
      } else {
        return this.contents[0];
      }
    },
  },
};
</script>

<style scoped>
@media (min-width: 820px) {
  .cvPoint {
    margin: 40px 0;
  }

  .cvBackground {
    padding: 60px 1em;
    background-image: url('/images/bg_microcms_screen_black.png');
    background-position: center center;
    background-repeat: no-repeat;
    background-size: cover;
    color: #fff;
    border-radius: 5px;
    margin-bottom: 40px;
  }

  .mainTitle {
    font-size: 20px;
    font-weight: bold;
    line-height: 1.7;
    margin-bottom: 20px;
    text-align: center;
  }

  .cvContainer {
    max-width: 457px;
    margin: auto;
  }

  .mainContents {
    line-height: 1.7;
    margin-bottom: 40px;
  }

  .buttonWrapper {
    text-align: center;
  }

  .button {
    display: inline-block;
    border: none;
    border-radius: 5px;
    background-color: var(--color-primary);
    color: #fff;
    text-align: center;
    font-size: 24px;
    font-weight: bold;
    padding: 16px 72px;
    cursor: pointer;

    &:hover {
      background-color: var(--color-primary-light);
    }
  }

  .background {
    background-color: #eee;
    border-radius: 5px;
    padding: 20px 35px 30px;
  }

  .subTitle {
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    margin-bottom: 10px;
  }

  .bottom {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: auto;
    grid-gap: 5%;
  }

  .buttonSmall {
    display: block;
    line-height: 1;
    border-radius: 5px;
    background-color: var(--color-primary);
    color: #fff;
    text-align: center;
    font-size: 18px;
    padding: 16px;
    font-weight: bold;
    cursor: pointer;
    margin-top: 20px;

    &:hover {
      background-color: var(--color-primary-light);
    }
  }

  .iconList {
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 30px auto 0;
  }

  .listItem {
    img {
      max-width: 32px;
      max-height: 32px;

      &[data-logo='x'] {
        max-width: 30px;
        max-height: 30px;
      }
    }
  }

  .listItem + .listItem {
    margin-left: 45px;
  }

  /* thumbnail theme */
  .cvPoint--thumbnail {
    background-color: #eee;
    padding: 60px 40px;

    img {
      max-width: 100%;
      vertical-align: top;
    }

    .upperContents {
      display: grid;
      grid-template-columns: 40% auto;
      align-items: center;
      grid-gap: 5%;
    }

    .cvBackground {
      background: inherit;
    }

    .background {
      background-color: #fff;
      padding: 25px 20px 30px;
    }

    .mainTitle {
      text-align: left;
      margin-bottom: 15px;
    }

    .mainText {
      margin-bottom: 0;
      font-size: 14px;
    }

    .buttonWrapper {
      margin-top: 30px;
    }
  }
}

@media (max-width: 820px) {
  .cvPoint {
    margin: 40px 0;
  }

  .cvBackground {
    padding: 40px 2em;
    background-image: url('/images/bg_microcms_screen_black.png');
    background-position: center center;
    background-repeat: no-repeat;
    background-size: cover;
    color: #fff;
    border-radius: 5px;
    margin-bottom: 20px;
  }

  .mainTitle {
    font-size: 18px;
    font-weight: bold;
    line-height: 1.7;
    margin-bottom: 10px;
  }

  .cvContainer {
    max-width: 457px;
    margin: auto;
  }

  .mainContents {
    line-height: 1.7;
    margin-bottom: 40px;
  }

  .buttonWrapper {
    text-align: center;
  }

  .button {
    display: block;
    border: none;
    border-radius: 5px;
    background-color: var(--color-primary);
    color: #fff;
    text-align: center;
    font-size: 18px;
    font-weight: bold;
    line-height: 1;
    padding: 16px 1em;
    cursor: pointer;

    &:hover {
      background-color: var(--color-primary-light);
    }
  }

  .background {
    background-color: #eee;
    border-radius: 5px;
    padding: 20px 35px 30px;
    margin-bottom: 20px;
  }

  .subTitle {
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    margin-bottom: 10px;
  }

  .buttonSmall {
    display: block;
    line-height: 1;
    border-radius: 5px;
    background-color: var(--color-primary);
    color: #fff;
    text-align: center;
    font-size: 18px;
    padding: 16px;
    font-weight: bold;
    cursor: pointer;
    margin-top: 20px;

    &:hover {
      background-color: var(--color-primary-light);
    }
  }

  .iconList {
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 30px auto 0;
  }

  .listItem {
    img {
      max-width: 32px;
      max-height: 32px;

      &[data-logo='x'] {
        max-width: 30px;
        max-height: 30px;
      }
    }
  }

  .listItem + .listItem {
    margin-left: 45px;
  }

  /* thumbnail theme */
  .cvPoint--thumbnail {
    background-color: #eee;
    padding: 40px 20px;

    img {
      max-width: 100%;
      vertical-align: top;
    }

    .cvBackground {
      background: inherit;
    }

    .thumbnail {
      margin-top: 20px;
    }

    .buttonWrapper {
      margin-top: 20px;
    }

    .button {
      padding: 20px 1em;
    }

    .background {
      background-color: #fff;
      padding: 25px 20px 30px;
    }

    .mainTitle {
      text-align: left;
      margin-bottom: 15px;
    }

    .mainText {
      margin-bottom: 0;
      font-size: 14px;
    }
  }
}
</style>


================================================
FILE: components/Cta.vue
================================================
<template>
  <div v-if="contents.length > 0" class="wrapper">
    <div v-for="(item, index) in getContents" :key="index" class="ctaItem">
      <div v-html="item.body"></div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    id: {
      type: String,
      required: true,
    },

    contents: {
      type: Array,
      required: false,
      default: () => [],
    },
  },

  computed: {
    getContents() {
      return this.contents;
    },
  },
};
</script>

<style scoped>
@media (min-width: 600px) {
  .wrapper {
    padding: 40px 0;
  }
  .ctaItem {
    & >>> h1 {
      font-size: 30px;
      font-weight: bold;
      margin: 40px 0 20px;
      background-color: #eee;
      padding: 10px 20px;
      border-radius: 5px;
    }

    & >>> h2 {
      font-size: 30px;
      font-weight: bold;
      margin: 40px 0 16px;
      border-bottom: 1px solid #ddd;
    }

    & >>> h3 {
      font-size: 24px;
      font-weight: bold;
      margin: 30px 0 12px;
    }

    & >>> h4 {
      font-size: 20px;
      font-weight: bold;
      margin: 24px 0 10px;
    }

    & >>> h5 {
      font-size: 16px;
      font-weight: bold;
      margin: 20px 0 6px;
    }

    & >>> p {
      line-height: 1.8;
      letter-spacing: 0.2px;

      & > code {
        color: var(--color-text-main);
        background-color: var(--color-bg-purple-light);
        border: 1px solid var(--color-border);
        border-radius: 3px;
        margin: 0 2px;
        padding: 2px 4px;
      }
    }

    & >>> em {
      font-style: italic;
    }

    & >>> ol {
      list-style-type: decimal;
      list-style-position: inside;

      & > li {
        line-height: 2;
      }
    }

    & >>> ul > li {
      line-height: 2;

      &::before {
        content: '-';
        margin-right: 10px;
      }
    }

    & >>> img {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> video {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> a {
      color: var(--color-purple);
      text-decoration: underline;
      word-wrap: break-word;
      word-break: break-all;
    }

    & >>> strong {
      background: linear-gradient(transparent 92%, var(--color-accent) 92%);
      font-weight: bold;
    }

    & >>> blockquote {
      background: url('/images/icon_quote.svg') no-repeat 20px 10px, #eee;
      background-size: 36px 36px;
      padding: 50px 20px 20px;
      margin: 20px 0;
      border-radius: 3px;
    }

    & >>> pre {
      border-radius: 3px;
      margin: 20px 0;
      white-space: pre-wrap;
      word-wrap: break-word;
      word-break: break-all;

      & > code {
        padding: 10px 20px;
        border-radius: 5px;
      }
    }

    & >>> iframe {
      height: auto;
      width: 100%;
      aspect-ratio: 16 / 9;
    }
  }
}
@media (max-width: 600px) {
  .wrapper {
    padding: 30px 0;
  }
  .ctaItem {
    font-size: 14px;

    & >>> h1 {
      font-size: 24px;
      font-weight: bold;
      margin: 40px 0 20px;
      background-color: #eee;
      padding: 10px 20px;
      border-radius: 5px;
    }

    & >>> h2 {
      font-size: 24px;
      font-weight: bold;
      margin: 36px 0 16px;
      border-bottom: 1px solid #ddd;
    }

    & >>> h3 {
      font-size: 20px;
      font-weight: bold;
      margin: 30px 0 12px;
    }

    & >>> h4 {
      font-size: 16px;
      font-weight: bold;
      margin: 24px 0 10px;
    }

    & >>> h5 {
      font-size: 14px;
      font-weight: bold;
      margin: 20px 0 6px;
    }

    & >>> p {
      line-height: 1.8;
      letter-spacing: 0.2px;

      & > code {
        color: var(--color-text-main);
        background-color: var(--color-bg-purple-light);
        border: 1px solid var(--color-border);
        border-radius: 3px;
        margin: 0 2px;
        padding: 2px 4px;
      }
    }

    & >>> em {
      font-style: italic;
    }

    & >>> ol {
      list-style-type: decimal;
      list-style-position: inside;

      & > li {
        line-height: 2;
      }
    }

    & >>> ul > li {
      line-height: 2;

      &::before {
        content: '-';
        margin-right: 10px;
      }
    }

    & >>> img {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> video {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> a {
      color: #331cbf;
      text-decoration: underline;
      word-wrap: break-word;
      word-break: break-all;
    }

    & >>> strong {
      background: linear-gradient(transparent 92%, var(--color-accent) 92%);
      font-weight: bold;
    }

    & >>> blockquote {
      background: url('/images/icon_quote.svg') no-repeat 20px 10px, #eee;
      background-size: 36px 36px;
      padding: 50px 20px 20px;
      margin: 20px 0;
      border-radius: 3px;
    }

    & >>> pre {
      border-radius: 3px;
      margin: 20px 0;
      white-space: pre-wrap;
      word-wrap: break-word;
      word-break: break-all;

      & > code {
        padding: 8px 16px;
        border-radius: 3px;
      }
    }

    & >>> iframe {
      height: auto;
      width: 100%;
      aspect-ratio: 16 / 9;
    }
  }
}
</style>


================================================
FILE: components/Footer.vue
================================================
<template>
  <footer class="footer">
    <h2 class="visuallyHidden">サイトマップ</h2>
    <div class="container">
      <ul class="sitemap">
        <li class="sitemapListItem">
          <section class="categoryTitle">
            <h3 class="categoryTitle">microCMSとは</h3>
            <ul class="menuList">
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/pricing">料金</a>
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/for-business"
                  >microCMSの特徴</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://blog.microcms.io/vs-wordpress/"
                  target="_blank"
                  >WordPressとの違い</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://microcms.io/solutions/multitenant"
                  >活用方法 - マルチテナント</a
                >
              </li>
              <li class="menuListItem">
                <hr />
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/interviews"
                  >導入事例インタビュー</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/projects"
                  >導入事例一覧</a
                >
              </li>
            </ul>
          </section>
        </li>

        <li class="sitemapListItem">
          <section>
            <h3 class="categoryTitle">機能</h3>
            <ul class="menuList">
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/features/schema"
                  >エディタ</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://microcms.io/features/custom-field"
                  >カスタムフィールド</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://microcms.io/features/contents-api"
                  >コンテンツAPI</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://document.microcms.io/management-api/introduction"
                  >マネジメントAPI</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/features/link-api"
                  >APIデータ連携</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://microcms.io/features/image-api"
                  >画像編集</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/features/sdk"
                  >SDK</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://microcms.io/features/authority"
                  >権限管理</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/features/workflow"
                  >ワークフロー</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/features/security"
                  >セキュリティ</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/features/support"
                  >サポート・トレーニング</a
                >
              </li>
            </ul>
          </section>
        </li>

        <li class="sitemapListItem">
          <section>
            <h3 class="categoryTitle">サポート</h3>
            <ul class="menuList">
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://help.microcms.io/ja/knowledge"
                  target="_blank"
                  >ヘルプ</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://document.microcms.io/manual/getting-started"
                  target="_blank"
                  >microCMSの始め方</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://blog.microcms.io/category/update/page/1/"
                  target="_blank"
                  >更新情報</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://document.microcms.io/"
                  target="_blank"
                  >ドキュメント</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://blog.microcms.io/"
                  target="_blank"
                  >ブログ</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://templates.microcms.io/"
                  target="_blank"
                  >テンプレート</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://github.com/microcmsio"
                  target="_blank"
                  >GitHub</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://roadmap.microcms.co.jp/"
                  target="_blank"
                  >ロードマップ</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://status.microcms.io/"
                  target="_blank"
                  >ステータス</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://www.youtube.com/channel/UCjdOq0rAB8Or1uHio9N0_pQ"
                  target="_blank"
                  >動画講座</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://microcms.io/community"
                  target="_blank"
                  >コミュニティ</a
                >
              </li>
            </ul>
          </section>
        </li>

        <li class="sitemapListItem">
          <section>
            <h3 class="categoryTitle">資料請求・セミナー</h3>
            <ul class="menuList">
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/ebook"
                  >お役立ち資料</a
                >
              </li>
              <li class="menuListItem">
                <hr />
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/seminars"
                  >セミナー情報</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/events"
                  >エンジニア向けイベント情報</a
                >
              </li>
            </ul>
            <h3 class="categoryTitle">メディアキット</h3>
            <ul class="menuList">
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/logo"
                  >ロゴガイドライン</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/ui-screenshot"
                  >UIスクリーンショット</a
                >
              </li>
            </ul>
          </section>
        </li>

        <li class="sitemapListItem">
          <section>
            <h3 class="categoryTitle">お問い合わせ</h3>
            <ul class="menuList">
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/contact"
                  >新規導入に関するご相談</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://microcms.io/partners#consultation"
                  >パートナー紹介のご相談</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/contact-support"
                  >ご利用中のサービスに関するご相談</a
                >
              </li>
            </ul>
            <h3 class="categoryTitle">パートナー</h3>
            <ul class="menuList">
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/for-partners"
                  >パートナーになる</a
                >
              </li>
              <li class="menuListItem">
                <a class="menuLink" href="https://microcms.io/partners"
                  >パートナーを探す</a
                >
              </li>
              <li class="menuListItem">
                <hr />
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://app.microcms.io/signin"
                  target="_blank"
                  >ログイン</a
                >
              </li>
              <li class="menuListItem">
                <a
                  class="menuLink"
                  href="https://app.microcms.io/signup"
                  target="_blank"
                  >新規登録</a
                >
              </li>
            </ul>
          </section>
        </li>
      </ul>
    </div>

    <ul class="lists">
      <li class="list">
        <a href="https://microcms.co.jp" target="_blank">運営会社</a>
      </li>
      <li class="list">
        <a href="https://microcms.co.jp/news" target="_blank">ニュース</a>
      </li>
      <li class="list">
        <a href="https://microcms.io/law">特定商取引法に基づく表記</a>
      </li>
      <li class="list">
        <a href="https://microcms.io/terms">利用規約</a>
      </li>
      <li class="list">
        <a href="https://microcms.io/policy">プライバシーポリシー</a>
      </li>
      <li class="list">
        <a href="https://microcms.io/security-policy">情報セキュリティ方針</a>
      </li>
    </ul>
    <p class="cr">Copyright © microcms.io All rights reserved.</p>
  </footer>
</template>

<style scoped>
.visuallyHidden {
  position: absolute;
  top: 0;
  left: 0;
  white-space: nowrap;
  width: 1px;
  height: 1px;
  overflow: hidden;
  border: 0;
  padding: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  margin: -1px;
}

@media (min-width: 980px) {
  .footer {
    position: relative;
    padding: 80px 40px 60px;
    z-index: 10;
    color: #fff;
    background-color: #2b2c30;
    margin-top: 120px;
  }

  .container {
    max-width: 1200px;
    margin: auto;
  }

  .sitemap {
    display: flex;
  }

  .sitemapListItem {
    width: calc(100% / 5);
    padding-right: 15px;
  }

  .categoryTitle {
    font-size: 18px;
    margin-bottom: 20px;
  }

  .menuList {
    hr {
      width: 80%;
      margin: 20px auto 20px 0;
    }

    + .categoryTitle {
      margin-top: 40px;
    }
  }

  .menuLink {
    font-size: 14px;
    color: #ddd;
    display: inline-block;
    padding: 9px 0;
  }

  .lists {
    max-width: 1200px;
    margin: 40px auto auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
  }

  .list {
    font-size: 14px;
    white-space: nowrap;
    margin-right: 30px;

    a {
      color: #ddd;
    }
  }

  .cr {
    max-width: 1200px;
    margin: 20px auto auto;
    color: #616269;
    font-size: 14px;
  }
}

@media (max-width: 980px) {
  .footer {
    position: relative;
    padding: 80px 30px 50px;
    z-index: 10;
    color: #fff;
    background-color: #2b2c30;
    margin-top: 20px;
  }

  .container {
    max-width: 1200px;
    margin: auto;
  }

  .sitemap {
    display: flex;
    flex-wrap: wrap;
    margin-bottom: 30px;
  }

  .sitemapListItem {
    width: calc((100% - 15px - 15px) / 2);
    padding-right: 15px;
    margin-bottom: 40px;
  }

  .categoryTitle {
    font-size: 18px;
    margin-bottom: 20px;
  }

  .menuList {
    hr {
      width: 80%;
      margin: 20px auto 20px 0;
    }
    + .categoryTitle {
      margin-top: 40px;
    }
  }

  .menuLink {
    font-size: 14px;
    color: #ddd;
    display: inline-block;
    padding: 10px 0;
  }

  .cr {
    font-size: 12px;
    margin-top: 20px;
    color: #616269;
  }

  .lists {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
  }

  .list {
    font-size: 14px;
    white-space: nowrap;
    margin-right: 30px;

    a {
      color: #fff;
    }
  }
}

@media screen and (max-width: 480px) {
  .footer {
    padding: 80px 30px 100px;
  }

  .sitemap {
    margin-bottom: 0;
  }

  .sitemapListItem {
    width: 100%;
  }

  .lists {
    flex-direction: column;
    align-items: flex-start;
  }

  .list {
    a {
      display: inline-block;
      padding: 10px 0;
    }
  }
}
</style>


================================================
FILE: components/Header.vue
================================================
<template>
  <div>
    <header class="header">
      <h1 class="logo">
        <a href="https://microcms.io">
          <img
            class="logoImg"
            src="/images/logo.svg"
            width="422"
            height="80"
            alt="microCMS"
          />
        </a>
      </h1>
      <button class="menuBtn" @click="toggleOpen()">
        <img src="/images/icon_menu.svg" width="24" height="24" alt="menu" />
      </button>
      <div v-if="open" class="mask" @click="setOpen(false)"></div>

      <div class="menu" :class="{ isOpen: open }">
        <ul class="lists isMobile">
          <li class="list">
            <a href="https://microcms.io/contact">新規導入に関するご相談</a>
          </li>
          <li class="list">
            <a href="https://microcms.io/contact-support"
              >ご利用中のサービスに関するご相談</a
            >
          </li>
          <li class="list">
            <a href="https://microcms.io/pricing">料金プラン</a>
          </li>
          <li class="list">
            <a href="https://microcms.io/projects">導入事例一覧</a>
          </li>
          <li class="list">
            <a href="https://help.microcms.io/ja/knowledge" target="_blank"
              >ヘルプ</a
            >
          </li>
          <li class="list">
            <a
              href="https://blog.microcms.io/category/update/page/1/"
              target="_blank"
              >更新情報</a
            >
          </li>
          <li class="list">
            <a href="https://blog.microcms.io/" target="_blank">ブログ</a>
          </li>
          <li class="list">
            <a href="https://document.microcms.io/" target="_blank"
              >ドキュメント</a
            >
          </li>
          <li class="list">
            <a href="https://microcms.io/seminars">セミナー情報</a>
          </li>
          <li class="list">
            <a href="https://microcms.io/ebook">お役立ち資料</a>
          </li>
          <li class="list">
            <a href="https://templates.microcms.io">テンプレート</a>
          </li>
        </ul>

        <ul class="lists isDesktop">
          <li
            v-for="(menu, index) in desktopHeaderMenu"
            :key="index"
            class="list"
            @mouseover="handleOpenMenu(index)"
            @mouseleave="handleCloseMenu(index)"
          >
            <component
              :is="menu.isDropDown ? 'button' : 'a'"
              :type="menu.isDropDown ? 'button' : ''"
              :href="menu.isDropDown ? '' : menu.path"
              :class="menu.isDropDown ? 'dropDown' : ''"
              v-text="menu.name"
            />

            <!-- dropdown menu -->
            <template v-if="menu.contents.length > 0">
              <transition name="fade">
                <ul v-show="openDropDownMenu[index]" class="dropDownMenu">
                  <li
                    v-for="(content, contentIndex) in menu.contents"
                    :key="contentIndex"
                    class="dropDownMenuList"
                  >
                    <a :href="content.path" v-text="content.name" />
                  </li>
                </ul>
              </transition>
            </template>
          </li>
        </ul>

        <ul class="lists authLinks">
          <li class="list authLinksItem">
            <a class="signin" href="https://app.microcms.io/signin">ログイン</a>
          </li>
          <li class="list noMargin">
            <a class="signup" :href="`https://app.microcms.io${params}`"
              >無料ではじめる</a
            >
          </li>
        </ul>
      </div>
    </header>
    <div class="empty"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      params: this.params || '',
      open: false,
      openDropDownMenu: [],
      desktopHeaderMenu: [
        {
          name: '機能',
          path: '',
          isDropDown: true,
          contents: [
            {
              name: 'エディタ',
              path: 'https://microcms.io/features/schema',
            },
            {
              name: 'カスタムフィールド',
              path: 'https://microcms.io/features/custom-field',
            },
            {
              name: 'コンテンツAPI',
              path: 'https://microcms.io/features/contents-api',
            },
            {
              name: 'マネジメントAPI',
              path: 'https://document.microcms.io/management-api/introduction',
            },
            {
              name: 'APIデータ連携',
              path: 'https://microcms.io/features/link-api',
            },
            {
              name: '画像編集',
              path: 'https://microcms.io/features/image-api',
            },
            {
              name: 'SDK',
              path: 'https://microcms.io/features/sdk',
            },
            {
              name: '権限管理',
              path: 'https://microcms.io/features/authority',
            },
            {
              name: 'ワークフロー',
              path: 'https://microcms.io/features/workflow',
            },
            {
              name: 'セキュリティ',
              path: 'https://microcms.io/features/security',
            },
            {
              name: 'サポート',
              path: 'https://microcms.io/features/support',
            },
          ],
        },
        {
          name: 'リソース',
          path: '',
          isDropDown: true,
          contents: [
            {
              name: '導入事例一覧',
              path: 'https://microcms.io/projects',
            },
            {
              name: '導入事例インタビュー',
              path: 'https://microcms.io/interviews',
            },
            {
              name: 'セミナー情報',
              path: 'https://microcms.io/seminars',
            },
            {
              name: 'イベント情報',
              path: 'https://microcms.io/events',
            },
            {
              name: 'ドキュメント',
              path: 'https://document.microcms.io/',
            },
            {
              name: 'ヘルプ',
              path: 'https://help.microcms.io/ja/knowledge',
            },
            {
              name: 'ブログ',
              path: 'https://blog.microcms.io/',
            },
            {
              name: 'テンプレート',
              path: 'https://templates.microcms.io/',
            },
          ],
        },
        {
          name: 'パートナー',
          path: '',
          isDropDown: true,
          contents: [
            {
              name: 'パートナーになる',
              path: 'https://microcms.io/for-partners',
            },
            {
              name: 'パートナーを探す',
              path: 'https://microcms.io/partners',
            },
          ],
        },
        {
          name: '料金プラン',
          path: 'https://microcms.io/pricing',
          isDropDown: false,
          contents: [],
        },
        {
          name: 'お役立ち資料',
          path: 'https://microcms.io/ebook',
          isDropDown: false,
          contents: [],
        },
        {
          name: 'お問い合わせ',
          path: '',
          isDropDown: true,
          contents: [
            {
              name: '新規導入に関するご相談',
              path: 'https://microcms.io/contact',
            },
            {
              name: 'パートナー紹介のご相談',
              path: 'https://microcms.io/partners#consultation',
            },
            {
              name: 'ご利用中のサービスに関するご相談',
              path: 'https://microcms.io/contact-support',
            },
          ],
        },
      ],
    };
  },
  created() {
    this.openDropDownMenu = Array(this.desktopHeaderMenu.length).fill(false);
  },
  mounted() {
    this.params = location.search || '';
  },
  methods: {
    setOpen(value) {
      this.open = value;
    },
    toggleOpen() {
      this.open = !this.open;
    },
    handleOpenMenu(index) {
      this.openDropDownMenu.splice(index, 1, true);
    },
    handleCloseMenu(index) {
      this.openDropDownMenu.splice(index, 1, false);
    },
  },
};
</script>

<style scoped>
@media (min-width: 1240px) {
  .header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    padding: 12px 40px;
    z-index: 20;
    border-bottom: 1px solid var(--color-border);
    background-color: #fff;
    font-size: 14px;
  }

  .empty {
    height: 80px;
    margin-bottom: 40px;
  }

  .logo {
    padding: 8px 0;
    margin-right: 30px;

    a {
      display: block;
      width: 160px;
      height: 30.3px;
    }
  }

  .logoImg {
    width: 160px;
    height: auto;
  }

  .menuBtn {
    display: none;
  }

  .menu {
    display: flex;
    padding: 8px 0;
  }

  .lists {
    display: flex;
    align-items: center;

    &:first-child::after {
      content: '';
      width: 1px;
      height: 30px;
      background-color: var(--color-text-off);
      margin-right: 40px;
    }

    &.isMobile {
      display: none;
    }

    &.isDesktop {
      display: flex;
    }

    &.authLinks {
      margin-left: 10px;
    }
  }

  .list {
    margin-right: 22px;
    padding: 8px 0;
    white-space: nowrap;
    position: relative;

    &.noMargin {
      margin: 0;
    }

    &.authLinksItem {
      margin-right: 16px;
    }

    a,
    a:visited {
      color: var(--color-text-main);

      &.signup {
        border-radius: 4px;
        background-color: var(--color-primary);
        color: #fff;
        text-align: center;
        padding: 12px 34px;
        font-size: 16px;
      }

      &.signin {
        border-radius: 4px;
        border: 1px solid var(--color-primary);
        color: var(--color-primary);
        text-align: center;
        padding: 11px 32px;
        font-size: 16px;
      }
    }
  }

  .dropDown {
    display: inline-block;
    padding-right: 20px;
    position: relative;
    appearance: none;
    border: none;
    background: inherit;
    font-size: inherit;

    &::after {
      content: '';
      display: inline-block;
      width: 16px;
      height: 16px;
      margin-left: 6px;
      background: url('/images/icon_arrow_bottom.svg') center center no-repeat;
      background-size: contain;
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
    }
  }

  .dropDownMenu {
    font-size: 14px;
    display: inline-block;
    position: absolute;
    background-color: #fff;
    top: 35px;
    right: 0px;
    padding: 8px 16px;
    box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    text-align: center;
  }

  .dropDownMenuList {
    a {
      display: block;
      transition: 0.3s all ease-in-out;
      padding: 10px 0;

      &:hover {
        opacity: 0.8;
      }
    }
  }

  .fade-enter-active,
  .fade-leave-active {
    transition: all 0.3s;
  }

  .fade-enter {
    transform: translateY(-10px);
  }

  .fade-enter,
  .fade-leave-to {
    opacity: 0;
  }
}

@media (max-width: 1240px) {
  .header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    background-color: #fff;
    padding: 16px;
    z-index: 20;
    border-bottom: 1px solid var(--color-border);
  }

  .empty {
    height: 61px;
  }

  .logo {
    display: inline-block;
    height: 28px;
  }

  .logoImg {
    width: auto;
    height: 28px;
  }

  .menuBtn {
    border: none;
    background: none;
    margin: 0;
    padding: 0;
    cursor: pointer;
  }

  .menu {
    position: absolute;
    left: 0;
    top: 61px;
    display: none;
    flex-direction: column-reverse;
    width: 100%;
    background-color: #fff;
    border-bottom: 1px solid var(--color-border);
    z-index: 2001;
    padding-top: 8px;

    &.isOpen {
      display: flex;
    }
  }

  .lists {
    padding: 8px 0;

    &:first-child {
      padding-top: 0;
    }

    &.isMobile {
      display: block;
    }

    &.isDesktop {
      display: none;
    }

    &.isMobileReverse {
      display: flex;
      flex-direction: column-reverse;
    }

    &.authLinks {
      display: flex;
      flex-direction: row-reverse;
      padding-right: 10px;
      padding-left: 10px;
      .list {
        width: calc(100% / 2);
        padding: 0 8px;
      }
    }
  }

  .list {
    padding: 0 16px;
    white-space: nowrap;

    a {
      display: block;
      color: var(--color-text-main);
      padding: 8px 0;

      &.signup {
        border-radius: 4px;
        background-color: var(--color-primary);
        color: #fff;
        text-align: center;
        font-weight: bold;
        margin-bottom: 8px;
      }

      &.signin {
        border-radius: 4px;
        color: var(--color-primary);
        border: 1px solid var(--color-primary);
        text-align: center;
        font-weight: bold;
        margin-bottom: 8px;
      }
    }

    &:last-child a {
      border-bottom: none;
    }
  }

  .mask {
    position: fixed;
    top: 61px;
    left: 0;
    bottom: 0;
    right: 0;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 2000;
  }
}
</style>


================================================
FILE: components/Latest.vue
================================================
<template>
  <div class="wrapper">
    <h1 class="pageTitle">最新の記事</h1>
    <ul>
      <li v-for="content in contents" :key="content.id" class="list">
        <nuxt-link :to="`/${content.id}`" class="link">
          {{ content.title }}
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    contents: {
      type: Array,
      required: false,
      default: () => [],
    },
  },
};
</script>

<style scoped>
@media (min-width: 1160px) {
  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    border-bottom: 1px solid #eee;

    &:last-child {
      border-bottom: none;
    }
  }

  .link {
    display: block;
    padding: 10px;
  }
}
@media (min-width: 820px) and (max-width: 1160px) {
  .wrapper {
    padding: 40px 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    border-bottom: 1px solid #eee;

    &:last-child {
      border-bottom: none;
    }
  }

  .link {
    display: block;
    padding: 10px;
  }
}
@media (max-width: 820px) {
  .wrapper {
    padding: 40px 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    border-bottom: 1px solid #eee;

    &:last-child {
      border-bottom: none;
    }
  }

  .link {
    display: block;
    padding: 10px;
  }
}
</style>


================================================
FILE: components/Logo.vue
================================================
<template>
  <svg
    class="NuxtLogo"
    width="245"
    height="180"
    viewBox="0 0 452 342"
    xmlns="http://www.w3.org/2000/svg"
  >
    <path
      d="M139 330l-1-2c-2-4-2-8-1-13H29L189 31l67 121 22-16-67-121c-1-2-9-14-22-14-6 0-15 2-22 15L5 303c-1 3-8 16-2 27 4 6 10 12 24 12h136c-14 0-21-6-24-12z"
      fill="#00C58E"
    />
    <path
      d="M447 304L317 70c-2-2-9-15-22-15-6 0-15 3-22 15l-17 28v54l39-67 129 230h-49a23 23 0 0 1-2 14l-1 1c-6 11-21 12-23 12h76c3 0 17-1 24-12 3-5 5-14-2-26z"
      fill="#108775"
    />
    <path
      d="M376 330v-1l1-2c1-4 2-8 1-12l-4-12-102-178-15-27h-1l-15 27-102 178-4 12a24 24 0 0 0 2 15c4 6 10 12 24 12h190c3 0 18-1 25-12zM256 152l93 163H163l93-163z"
      fill="#2F495E"
    />
  </svg>
</template>

<style>
.NuxtLogo {
  animation: 1s appear;
  margin: auto;
}

@keyframes appear {
  0% {
    opacity: 0;
  }
}
</style>


================================================
FILE: components/Meta.vue
================================================
<template>
  <div>
    <div class="upper">
      <template v-if="category && isSinglePage">
        <nuxt-link class="category" :to="`/category/${category.id}/page/1`">{{
          category.name
        }}</nuxt-link>
      </template>
      <template v-else-if="category">
        <span class="category">{{ category.name }}</span>
      </template>

      <ul v-if="tags" class="tag">
        <li v-for="tag in tags" :key="tag.id">
          <template v-if="isSinglePage">
            <nuxt-link :to="`/tag/${tag.id}/page/1`">{{ tag.name }}</nuxt-link>
          </template>
          <template v-else>
            <span>{{ tag.name }}</span>
          </template>
        </li>
      </ul>
    </div>

    <div class="meta">
      <span class="timestamp" title="公開日">
        <img src="/images/icon_clock.svg" width="20" height="20" alt />
        <time :datetime="$dayjs(createdAt).format('YYYY-MM-DD')">
          {{ $dayjs(createdAt).format('YYYY/MM/DD') }}
        </time>
      </span>
      <span v-if="isRevised" class="timestamp" title="更新日">
        <img src="/images/icon_update.svg" width="20" height="20" alt />
        <time :datetime="$dayjs(revisedAt).format('YYYY-MM-DD')">
          {{ $dayjs(revisedAt).format('YYYY/MM/DD') }}
        </time>
      </span>
      <span v-if="author" class="author">
        <img src="/images/icon_author.svg" width="20" height="21" alt />
        {{ author.name }}
      </span>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    createdAt: {
      type: String,
      required: true,
    },
    revisedAt: {
      type: String,
      required: false,
      default: undefined,
    },
    author: {
      type: Object,
      required: false,
      default: undefined,
    },
    category: {
      type: Object,
      required: false,
      default: undefined,
    },
    tags: {
      type: Array,
      required: false,
      default: undefined,
    },
    isSinglePage: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  computed: {
    isRevised() {
      if (this.revisedAt === undefined) {
        return false;
      }
      return (
        this.$dayjs(this.revisedAt).format('YYYY-MM-DD') !==
        this.$dayjs(this.createdAt).format('YYYY-MM-DD')
      );
    },
  },
};
</script>

<style scoped>
@media (min-width: 600px) {
  .upper {
    display: flex;
    align-items: center;
    margin-top: 10px;
  }

  .meta {
    padding: 10px 0 40px;
    display: flex;
    align-items: center;
  }

  .category {
    display: inline-block;
    padding: 2px 8px;
    border: 1px solid #331cbf;
    color: #331cbf;
    white-space: nowrap;
    border-radius: 3px;
    font-size: 14px;
    margin: 0 0 2px;
  }

  .tag {
    margin: 0 0 0 16px;

    li {
      color: #331cbf;
      font-size: 16px;
      display: inline-block;
      margin-right: 20px;

      a,
      span {
        color: inherit;
        display: inline-block;
        padding-left: 22px;
        position: relative;

        &::before {
          content: '';
          display: inline-block;
          background: url('/images/icon_tag_navy.svg') center no-repeat;
          background-size: contain;
          width: 16px;
          height: 16px;
          position: absolute;
          top: 50%;
          left: 0;
          transform: translateY(-50%);
        }
      }
    }
  }

  .timestamp {
    display: inline-flex;
    align-items: center;
    color: #616269;

    margin-right: 20px;
    white-space: nowrap;

    img {
      margin-right: 6px;
      height: 16px;
      width: auto;
    }
  }

  .author {
    display: inline-flex;
    align-items: center;
    color: #616269;
    white-space: nowrap;

    img {
      margin-right: 6px;
      height: 16px;
      width: auto;
    }
  }
}
@media (max-width: 600px) {
  .meta {
    padding: 4px 0 30px;
    font-size: 14px;
  }

  .category {
    display: inline-block;
    padding: 2px 8px;
    border: 1px solid #331cbf;
    color: #331cbf;
    white-space: nowrap;
    border-radius: 3px;
    font-size: 14px;
    margin: 10px 0 4px;
  }

  .tag {
    margin: 2px 0 4px;

    li {
      color: #331cbf;
      font-size: 14px;
      display: inline-block;
      margin-right: 16px;

      a,
      span {
        color: inherit;
        display: inline-block;
        padding-left: 18px;
        position: relative;

        &::before {
          content: '';
          display: inline-block;
          background: url('/images/icon_tag_navy.svg') center no-repeat;
          background-size: contain;
          width: 12px;
          height: 12px;
          position: absolute;
          top: 50%;
          left: 0;
          transform: translateY(-50%);
        }
      }
    }
  }

  .timestamp {
    display: inline-flex;
    align-items: center;
    color: #616269;
    margin-right: 20px;
    white-space: nowrap;

    img {
      margin-right: 6px;
      height: 14px;
      width: auto;
    }
  }

  .author {
    display: inline-flex;
    align-items: center;
    color: #616269;
    white-space: nowrap;

    img {
      margin-right: 6px;
      height: 14px;
      width: auto;
    }
  }
}
</style>


================================================
FILE: components/NextBlogNavigation.vue
================================================
<template>
  <div v-if="previous || next" class="navigation">
    <ul>
      <li class="previous">
        <nuxt-link v-if="previous && previous.id" :to="`/${previous.id}`">{{
          previous.title
        }}</nuxt-link>
      </li>
      <li class="next">
        <nuxt-link v-if="next && next.id" :to="`/${next.id}`">{{
          next.title
        }}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    previous: {
      type: Object,
      required: false,
      default: undefined,
    },
    next: {
      type: Object,
      required: false,
      default: undefined,
    },
  },
};
</script>

<style scoped>
.navigation {
  margin: 60px 0;

  ul {
    display: flex;
    justify-content: space-between;

    li {
      width: 40%;
      position: relative;

      a {
        color: var(--color-purple);
        text-decoration: underline;
      }
    }

    .previous {
      padding-left: 32px;

      ::before {
        content: '';
        display: block;
        position: absolute;
        top: 8px;
        left: 0;
        width: 8px;
        height: 8px;
        border-bottom: 2px solid var(--color-purple);
        border-left: 2px solid var(--color-purple);
        transform: translateX(50%) rotate(45deg);
      }
    }

    .next {
      padding-right: 32px;
      text-align: right;

      ::before {
        content: '';
        display: block;
        position: absolute;
        top: 8px;
        right: 0;
        width: 8px;
        height: 8px;
        border-top: 2px solid var(--color-purple);
        border-right: 2px solid var(--color-purple);
        transform: translateX(-50%) rotate(45deg);
      }
    }
  }
}

@media (max-width: 600px) {
  .navigation {
    font-size: 14px;
  }
}
</style>


================================================
FILE: components/Pagination.vue
================================================
<template>
  <div class="wrapper">
    <ul class="pager">
      <li v-if="current > 1" class="page arrow">
        <nuxt-link :to="getPath(current - 1)">
          <img
            src="/images/icon_arrow_left.svg"
            width="24"
            height="24"
            alt="前のページへ"
          />
        </nuxt-link>
      </li>
      <li v-if="3 < current" class="page">
        <nuxt-link :to="getPath(1)">
          1
        </nuxt-link>
      </li>
      <li v-if="4 < current" class="omission">
        ...
      </li>
      <li
        v-for="p in pager"
        v-show="current - 3 <= p && p <= current + 1"
        :key="p"
        class="page"
        :class="{ active: current === p + 1 }"
      >
        <nuxt-link :to="getPath(p + 1)">
          {{ p + 1 }}
        </nuxt-link>
      </li>
      <li v-if="current + 3 < pager.length" class="omission">
        ...
      </li>
      <li v-if="current + 2 < pager.length" class="page">
        <nuxt-link :to="getPath(pager.length)">
          {{ pager.length }}
        </nuxt-link>
      </li>
      <li v-if="current < pager.length" class="page arrow">
        <nuxt-link :to="getPath(current + 1)">
          <img
            src="/images/icon_arrow_right.svg"
            width="24"
            height="24"
            alt="次のページへ"
          />
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    pager: {
      type: Array,
      required: false,
      default: () => [],
    },
    current: {
      type: Number,
      required: true,
    },
    category: {
      type: Object,
      required: false,
      default: undefined,
    },
    tag: {
      type: Object,
      required: false,
      default: undefined,
    },
    author: {
      type: Object,
      required: false,
      default: undefined,
    },
  },
  methods: {
    getPath(p) {
      if (this.category !== undefined) {
        return `/category/${this.category.id}/page/${p}`;
      } else if (this.tag !== undefined) {
        return `/tag/${this.tag.id}/page/${p}`;
      } else if (this.author !== undefined) {
        return `/author/${this.author.id}/page/${p}`;
      } else {
        return `/page/${p}`;
      }
    },
  },
};
</script>

<style scoped>
.wrapper {
  padding: 16px 0;
}

.pager {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  padding: 40px 0 0;
}

.omission {
  color: var(--color-text-off);
  margin: 4px 12px;
}

.page {
  width: 40px;
  height: 40px;
  border-radius: 5px;
  margin: 4px;

  &.arrow {
    margin: 4px 12px;
  }

  &.active {
    background-color: var(--color-blue);

    a,
    a:hover {
      color: #fff;
    }
  }

  a {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
    color: var(--color-text-sub);

    &:hover {
      color: var(--color-blue);
    }
  }
}
</style>


================================================
FILE: components/Partner.vue
================================================
<template>
  <div class="wrapper">
    <div v-if="partner" class="container">
      <picture v-if="partner.logo">
        <source
          type="image/webp"
          :data-srcset="partner.logo.url + '?fit=crop&w=100&h=100&fm=webp'"
        />
        <img
          :data-src="partner.logo.url + '?fit=crop&w=100&h=100&q=100'"
          class="image lazyload"
          alt
        />
      </picture>
      <dl class="content">
        <dt class="name">
          <span class="company">{{ partner.company }}</span>
          <span class="label">認定パートナー</span>
        </dt>
        <dd v-if="partner.description" class="text">
          {{ partner.description }}
        </dd>
        <dd v-if="partner.url" class="url">
          <a :href="partner.url" target="_blank" rel="noopener noreferrer">{{
            partner.url
          }}</a>
        </dd>
        <dd>
          <a
            class="button"
            :href="`https://microcms.io/partners/${partner.id}`"
            target="partner"
            >制作を依頼する</a
          >
        </dd>
      </dl>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    partner: {
      type: Object,
      required: false,
      default: undefined,
    },
  },
};
</script>

<style scoped>
.container {
  display: flex;
}

.content {
  flex: 1;
}

.name {
  display: flex;
  align-items: center;
  font-weight: bold;
  flex-wrap: wrap;
}

.company {
  margin-right: 8px;
  margin-bottom: 10px;
}

.label {
  border: 1px solid var(--color-accent);
  border-radius: 3px;
  padding: 2px 8px;
  font-size: 12px;
  color: var(--color-accent);
  white-space: nowrap;
  margin-bottom: 10px;
}

.text {
  font-size: 14px;
  margin-bottom: 4px;
}

.url {
  font-size: 14px;
  margin-bottom: 10px;

  a {
    color: var(--color-blue);
  }
}

.button {
  display: inline-block;
  border: none;
  border-radius: 5px;
  background: linear-gradient(to right bottom, #5630af, #3067af);
  color: #fff;
  text-align: center;
  font-size: 16px;
  font-weight: bold;
  padding: 8px 24px;
  cursor: pointer;
  margin-top: 10px;

  &:hover {
    background: linear-gradient(to right bottom, #46209f, #20579f);
  }
}

@media (min-width: 600px) {
  .wrapper {
    position: relative;
    padding: 30px 20px 20px;
  }
  .title {
    position: absolute;
    top: 0;
    right: 20px;
    background-color: #2b2c30;
    color: #fff;
    font-size: 14px;
    padding: 2px 10px;
    border-radius: 0 0 5px 5px;
  }
  .image {
    width: 100px;
    height: 100px;
    margin-right: 20px;
  }
}

@media (max-width: 600px) {
  .wrapper {
    position: relative;
    padding: 30px 0px 20px;
  }
  .title {
    position: absolute;
    top: 0;
    right: 0;
    background-color: #2b2c30;
    color: #fff;
    font-size: 14px;
    padding: 2px 10px;
    border-radius: 0 0 5px 5px;
  }
  .image {
    width: 60px;
    height: 60px;
    margin-right: 10px;
  }
}
</style>


================================================
FILE: components/PopularArticles.vue
================================================
<template>
  <div class="wrapper">
    <h1 class="pageTitle">人気の記事</h1>
    <ul>
      <li v-for="content in contents" :key="content.id" class="list">
        <nuxt-link :to="`/${content.id}/`" class="link">
          <picture v-if="content.ogimage">
            <source
              type="image/webp"
              :data-srcset="content.ogimage.url + '?w=560&fm=webp'"
            />
            <img
              :data-src="content.ogimage.url + '?w=560&q=100'"
              :width="content.ogimage.width"
              :height="content.ogimage.height"
              class="image lazyload"
              alt
            />
          </picture>
          <picture v-else>
            <source type="image/webp" :data-srcset="content.defaultOgimage" />
            <img
              :data-src="content.defaultOgimage"
              class="image lazyload"
              alt
            />
          </picture>
          <p class="title">{{ content.title }}</p>
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    contents: {
      type: Array,
      required: false,
      default: () => [],
    },
  },
};
</script>

<style scoped>
@media (min-width: 1160px) {
  .wrapper {
    padding-bottom: 40px;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    margin-bottom: 10px;

    &:last-child {
      margin-bottom: 0;
    }
  }

  .link {
    display: block;
    padding: 10px;
  }

  .image {
    width: 280px;
    height: auto;
    border-radius: 5px;
  }

  .title {
    padding-top: 10px;
  }
}
@media (min-width: 520px) and (max-width: 1160px) {
  .wrapper {
    padding-top: 40px;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    margin-bottom: 10px;

    &:last-child {
      margin-bottom: 0;
    }
  }

  .link {
    display: flex;
    padding: 10px;
  }

  .image {
    width: 140px;
    height: auto;
    border-radius: 5px;
  }

  .title {
    padding-left: 20px;
  }
}
@media (max-width: 520px) {
  .wrapper {
    padding: 40px 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    margin-bottom: 10px;

    &:last-child {
      margin-bottom: 0;
    }
  }

  .link {
    display: block;
    padding: 10px;
  }

  .image {
    width: 100%;
    height: auto;
    border-radius: 5px;
  }

  .title {
    padding-top: 10px;
  }
}
</style>


================================================
FILE: components/Post.vue
================================================
<template>
  <div class="post" v-html="body"></div>
</template>

<script>
export default {
  props: {
    body: {
      type: String,
      required: true,
      default: '',
    },
  },
};
</script>

<style scoped>
@media (min-width: 600px) {
  .post {
    & >>> h1 {
      font-size: 30px;
      font-weight: bold;
      margin: 40px 0 20px;
      background-color: #eee;
      padding: 10px 20px;
      border-radius: 5px;
    }

    & >>> h2 {
      font-size: 30px;
      font-weight: bold;
      margin: 40px 0 16px;
      border-bottom: 1px solid #ddd;
    }

    & >>> h3 {
      font-size: 24px;
      font-weight: bold;
      margin: 30px 0 12px;
    }

    & >>> h4 {
      font-size: 20px;
      font-weight: bold;
      margin: 24px 0 10px;
    }

    & >>> h5 {
      font-size: 16px;
      font-weight: bold;
      margin: 20px 0 6px;
    }

    & >>> p {
      line-height: 1.8;
      letter-spacing: 0.2px;

      & > code {
        color: var(--color-text-main);
        background-color: var(--color-bg-purple-light);
        border: 1px solid var(--color-border);
        border-radius: 3px;
        margin: 0 2px;
        padding: 2px 4px;
      }
    }

    & >>> em {
      font-style: italic;
    }

    & >>> ol {
      list-style-type: decimal;
      list-style-position: inside;

      & > li {
        line-height: 2;
      }
    }

    & >>> ul > li {
      line-height: 2;

      &::before {
        content: '-';
        margin-right: 10px;
      }
    }

    & >>> img {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> video {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> a {
      color: var(--color-purple);
      text-decoration: underline;
      word-wrap: break-word;
      word-break: break-all;
    }

    & >>> strong {
      background: linear-gradient(transparent 92%, var(--color-accent) 92%);
      font-weight: bold;
    }

    & >>> blockquote {
      background: url('/images/icon_quote.svg') no-repeat 20px 10px, #eee;
      background-size: 36px 36px;
      padding: 50px 20px 20px;
      margin: 20px 0;
      border-radius: 3px;
    }

    & >>> pre {
      border-radius: 3px;
      margin: 20px 0;
      white-space: pre-wrap;
      word-wrap: break-word;
      word-break: break-all;

      & > code {
        padding: 10px 20px;
        border-radius: 5px;
      }
    }
  }
}
@media (max-width: 600px) {
  .post {
    font-size: 14px;

    & >>> h1 {
      font-size: 24px;
      font-weight: bold;
      margin: 40px 0 20px;
      background-color: #eee;
      padding: 10px 20px;
      border-radius: 5px;
    }

    & >>> h2 {
      font-size: 24px;
      font-weight: bold;
      margin: 36px 0 16px;
      border-bottom: 1px solid #ddd;
    }

    & >>> h3 {
      font-size: 20px;
      font-weight: bold;
      margin: 30px 0 12px;
    }

    & >>> h4 {
      font-size: 16px;
      font-weight: bold;
      margin: 24px 0 10px;
    }

    & >>> h5 {
      font-size: 14px;
      font-weight: bold;
      margin: 20px 0 6px;
    }

    & >>> p {
      line-height: 1.8;
      letter-spacing: 0.2px;

      & > code {
        color: var(--color-text-main);
        background-color: var(--color-bg-purple-light);
        border: 1px solid var(--color-border);
        border-radius: 3px;
        margin: 0 2px;
        padding: 2px 4px;
      }
    }

    & >>> em {
      font-style: italic;
    }

    & >>> ol {
      list-style-type: decimal;
      list-style-position: inside;

      & > li {
        line-height: 2;
      }
    }

    & >>> ul > li {
      line-height: 2;

      &::before {
        content: '-';
        margin-right: 10px;
      }
    }

    & >>> img {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> video {
      max-width: 100%;
      margin: 40px 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    & >>> a {
      color: #331cbf;
      text-decoration: underline;
      word-wrap: break-word;
      word-break: break-all;
    }

    & >>> strong {
      background: linear-gradient(transparent 92%, var(--color-accent) 92%);
      font-weight: bold;
    }

    & >>> blockquote {
      background: url('/images/icon_quote.svg') no-repeat 20px 10px, #eee;
      background-size: 36px 36px;
      padding: 50px 20px 20px;
      margin: 20px 0;
      border-radius: 3px;
    }

    & >>> pre {
      border-radius: 3px;
      margin: 20px 0;
      white-space: pre-wrap;
      word-wrap: break-word;
      word-break: break-all;

      & > code {
        padding: 8px 16px;
        border-radius: 3px;
      }
    }

    & >>> iframe {
      height: auto;
      width: 100%;
      aspect-ratio: 16 / 9;
    }
  }
}
</style>


================================================
FILE: components/README.md
================================================
# COMPONENTS

**This directory is not required, you can delete it if you don't want to use it.**

The components directory contains your Vue.js Components.

_Nuxt.js doesn't supercharge these components._


================================================
FILE: components/RelatedBlogs.vue
================================================
<template>
  <div class="wrapper">
    <h2 class="pageTitle">関連記事</h2>
    <ul class="lists">
      <li v-for="blog in blogs" :key="blog.id" class="list">
        <nuxt-link :to="`/${blog.id}/`" class="link">
          <picture v-if="blog.ogimage">
            <source
              type="image/webp"
              :data-srcset="blog.ogimage.url + '?w=820&fm=webp'"
            />
            <img
              :data-src="blog.ogimage.url + '?w=820'"
              class="img lazyload"
              alt
            />
          </picture>
          <picture v-else>
            <source type="image/webp" :data-srcset="blog.defaultOgimage" />
            <img :data-src="blog.defaultOgimage" class="img lazyload" alt />
          </picture>
          <dl class="content">
            <dt class="title">{{ blog.title }}</dt>
            <dd>
              <Meta
                :created-at="blog.createdAt"
                :author="blog.writer"
                :category="blog.category"
              />
            </dd>
          </dl>
        </nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    blogs: {
      type: Array,
      required: false,
      default: () => [],
    },
  },
};
</script>

<style scoped>
@media (min-width: 1160px) {
  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin: 60px 0 40px;
    border-radius: 5px;
  }

  .lists {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
  }

  .list {
    width: 340px;
    border-radius: 5px;
    transition: box-shadow 0.1s linear;

    &:hover {
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }
  }

  .img {
    width: 340px;
    height: 178px;
    border-radius: 5px 5px 0 0;
  }

  .content {
    padding: 10px 10px 0;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
  }
}
@media (min-width: 820px) and (max-width: 1160px) {
  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin: 60px 0 40px;
    border-radius: 5px;
  }

  .lists {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
  }

  .list {
    width: 300px;
    border-radius: 5px;
    transition: box-shadow 0.1s linear;

    &:hover {
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }
  }

  .img {
    width: 300px;
    height: 157px;
    border-radius: 5px 5px 0 0;
  }

  .content {
    padding: 10px 10px 0;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
  }
}
@media (max-width: 820px) {
  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin: 40px 0 20px;
    border-radius: 5px;
  }

  .list {
    width: 100%;
    border-radius: 5px;
  }

  .img {
    width: 100%;
    border-radius: 5px;
  }

  .title {
    padding-top: 5px;
    font-size: 18px;
    font-weight: bold;
  }
}
</style>


================================================
FILE: components/Search.vue
================================================
<template>
  <label class="label">
    サイト内検索
    <input
      class="input"
      type="text"
      @keypress="setSearchable"
      @keypress.enter="search"
    />
  </label>
</template>

<script>
export default {
  data() {
    return {
      searchable: false,
    };
  },
  methods: {
    setSearchable() {
      this.searchable = true;
    },
    search(e) {
      if (!e.target.value.trim() || !this.searchable) {
        return;
      }
      this.$router.push({ path: '/search', query: { q: e.target.value } });
    },
  },
};
</script>

<style scoped>
.label {
  display: block;
  font-size: 14px;
  font-weight: bold;
  color: var(--color-text-sub);
}
.input {
  border: 1px solid var(--color-border);
  width: 100%;
  box-sizing: border-box;
  margin-top: 5px;
  border-radius: 5px;
  height: 40px;
  font-size: 16px;
  background: url('/images/icon_search.svg') no-repeat 10px center,
    var(--color-bg-purple-light);
  padding-left: 40px;
  box-shadow: none;
  -webkit-appearance: none;
  transition: box-shadow 0.2s ease;

  &:hover {
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1) inset;
  }

  &:focus {
    outline: none;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1) inset;
  }
}
</style>


================================================
FILE: components/Share.vue
================================================
<template>
  <div class="share">
    <ul class="shareLists">
      <li class="shareList">
        <a :href="twitterLink" target="_blank" rel="noopener noreferrer">
          <img src="/images/icon_x.svg" alt="X" />
        </a>
      </li>
      <li class="shareList">
        <a :href="facebookLink" target="_blank" rel="noopener noreferrer">
          <img src="/images/icon_facebook.svg" alt="Facebook" />
        </a>
      </li>
      <li class="shareList">
        <a :href="hatenaLink" target="_blank" rel="noopener noreferrer">
          <img src="/images/icon_hatena.svg" alt="はてなブックマーク" />
        </a>
      </li>
      <li class="shareList">
        <a
          href="https://blog.microcms.io/feed.xml"
          target="_blank"
          rel="noopener noreferrer"
        >
          <img src="/images/icon_feed.svg" alt="フィード" />
        </a>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    id: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
  },
  computed: {
    twitterLink() {
      return `https://twitter.com/intent/tweet?text=${encodeURIComponent(
        this.title
      )}&url=https://blog.microcms.io/${this.id}/&hashtags=microcms`;
    },
    facebookLink() {
      return `https://www.facebook.com/sharer.php?u=https://blog.microcms.io/${this.id}/`;
    },
    hatenaLink() {
      return `https://b.hatena.ne.jp/entry/https://blog.microcms.io/${this.id}/`;
    },
  },
};
</script>

<style scoped>
@media (min-width: 820px) {
  .share {
    width: 24px;
    padding-top: 16px;

    img {
      max-width: 24px;
      max-height: 24px;
    }
  }

  .shareLists {
    display: block;
    position: sticky;
    top: 120px;
  }

  .shareList {
    text-align: center;
    margin-bottom: 20px;
  }
}
@media (min-width: 600px) and (max-width: 820px) {
  .share {
    width: 24px;
    padding-top: 16px;

    img {
      max-width: 24px;
      max-height: 24px;
    }
  }

  .shareLists {
    display: block;
    position: sticky;
    top: 100px;
  }

  .shareList {
    text-align: center;
    margin-bottom: 20px;
  }
}

@media (max-width: 600px) {
  .share {
    margin: 40px 0 0;

    img {
      max-height: 20px;
    }
  }

  .shareLists {
    display: flex;
    justify-content: center;
  }

  .shareList {
    text-align: center;
    margin: 0 20px;
  }
}
</style>


================================================
FILE: components/ShareButtons.vue
================================================
<template>
  <div class="share">
    <h3 class="title">記事をシェアする</h3>
    <ul class="shareLists">
      <li class="shareList">
        <a
          :href="twitterLink"
          target="_blank"
          rel="noopener noreferrer"
          class="button twitter"
        >
          <img src="/images/icon_x.svg" alt="X" class="icon" />
          ポストする
        </a>
      </li>
      <li class="shareList">
        <a
          :href="facebookLink"
          target="_blank"
          rel="noopener noreferrer"
          class="button facebook"
        >
          <img src="/images/icon_facebook.svg" alt="Facebook" class="icon" />
          シェアする
        </a>
      </li>
      <li class="shareList">
        <a
          :href="hatenaLink"
          target="_blank"
          rel="noopener noreferrer"
          class="button hatena"
        >
          <img
            src="/images/icon_hatena.svg"
            alt="はてなブックマーク"
            class="icon"
          />
          ブックマークする
        </a>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    id: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
  },
  computed: {
    twitterLink() {
      return `https://twitter.com/intent/tweet?text=${encodeURIComponent(
        this.title
      )}&url=https://blog.microcms.io/${this.id}/&hashtags=microcms`;
    },
    facebookLink() {
      return `https://www.facebook.com/sharer.php?u=https://blog.microcms.io/${this.id}/`;
    },
    hatenaLink() {
      return `https://b.hatena.ne.jp/entry/https://blog.microcms.io/${this.id}/`;
    },
  },
};
</script>

<style scoped>
.share {
  background-color: #f7f7fc;
  padding: 24px;
  border-radius: 5px;
  margin-top: 40px;
}

.title {
  font-weight: bold;
  font-size: 14px;
  color: var(--color-text-sub);
  padding-bottom: 16px;
  text-align: center;
}

.shareLists {
  display: flex;
  justify-content: center;
}

.button {
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 5px;
  padding: 12px 0;
  margin: 0 8px;
  font-size: 14px;
  font-weight: bold;

  &:hover {
    opacity: 0.8;
  }

  img {
    margin-right: 16px;
  }
}

.twitter {
  border: 1px solid var(--color-social-x);
  color: var(--color-social-x);
}

.facebook {
  border: 1px solid var(--color-social-facebook);
  color: var(--color-social-facebook);
}

.hatena {
  border: 1px solid var(--color-social-hatena);
  color: var(--color-social-hatena);
}

.icon {
  max-width: 18px;
  max-height: 18px;
}

@media (min-width: 820px) {
  .button {
    width: 200px;
  }
}
@media (min-width: 600px) and (max-width: 820px) {
  .button {
    width: 180px;
    font-size: 12px;
  }
}

@media (max-width: 600px) {
  .shareLists {
    flex-direction: column;
  }

  .button {
    width: 100%;
    font-size: 12px;
    margin-left: 0;
    margin-bottom: 16px;
  }
}
</style>


================================================
FILE: components/Tags.vue
================================================
<template>
  <div class="wrapper">
    <h1 class="pageTitle">タグ</h1>
    <ul class="list">
      <li v-for="tag in tags" :key="tag.id" class="listItem">
        <nuxt-link :to="`/tag/${tag.id}/page/1`" class="link">{{
          tag.name
        }}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    tags: {
      type: Array,
      required: true,
    },
  },
};
</script>

<style scoped>
@media (min-width: 820px) {
  .wrapper {
    padding: 40px 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    padding: 0 10px;
  }

  .listItem {
    display: inline-block;
    margin-right: 16px;
    padding: 4px 0;
  }

  .link {
    font-size: 16px;
    display: inline-block;
    padding-left: 18px;
    position: relative;

    &::before {
      content: '';
      display: inline-block;
      background: url('/images/icon_tag.svg') center no-repeat;
      background-size: contain;
      width: 14px;
      height: 14px;
      position: absolute;
      top: 50%;
      left: 0;
      transform: translateY(-50%);
    }
  }
}

@media (max-width: 820px) {
  .wrapper {
    padding: 40px 0 0;
  }

  .pageTitle {
    font-size: 20px;
    font-weight: bold;
    background-color: #eee;
    padding: 6px 10px;
    margin-bottom: 10px;
    border-radius: 5px;
  }

  .list {
    padding: 0 10px;
  }

  .listItem {
    display: inline-block;
    margin-right: 16px;
    padding: 4px 0;
  }

  .link {
    font-size: 16px;
    display: inline-block;
    padding-left: 18px;
    position: relative;

    &::before {
      content: '';
      display: inline-block;
      background: url('/images/icon_tag.svg') center no-repeat;
      background-size: contain;
      width: 12px;
      height: 12px;
      position: absolute;
      top: 50%;
      left: 0;
      transform: translateY(-50%);
    }
  }
}
</style>


================================================
FILE: components/Toc.vue
================================================
<template>
  <div v-if="visible" class="wrapper">
    <h4 class="title">目次</h4>
    <ul class="lists">
      <li v-for="item in toc" :key="item.id" :class="`list ${item.name}`">
        <n-link v-scroll-to="`#${item.id}`" to>
          {{ item.text }}
        </n-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    toc: {
      type: Array,
      required: true,
      default: () => [],
    },
    visible: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
};
</script>

<style scoped>
.wrapper {
  background-color: #f7f7fc;
  border-radius: 5px;
  padding: 20px;
  margin-bottom: 40px;
}
.title {
  font-size: 16px;
  font-weight: bold;
  margin: 0 0 10px;
  border-radius: 5px;
}

.list {
  padding: 5px 0;
  font-size: 14px;
  border-bottom: 1px solid #e7e7f3;

  a:hover {
    color: #331cbf;
  }

  &::before {
    content: '-';
    margin-right: 5px;
    color: #cacae7;
  }

  &.h2 {
    margin-left: 10px;
  }

  &.h3 {
    margin-left: 20px;
  }
}
</style>


================================================
FILE: components/Writer.vue
================================================
<template>
  <div class="wrapper">
    <h2 class="title">ABOUT ME</h2>
    <div class="container">
      <picture v-if="writer.image">
        <source
          type="image/webp"
          :data-srcset="writer.image.url + '?fit=crop&w=100&h=100&fm=webp'"
        />
        <img
          :data-src="writer.image.url + '?fit=crop&w=100&h=100&q=100'"
          class="image lazyload"
          alt
        />
      </picture>
      <dl class="content">
        <dt class="name">
          <nuxt-link :to="`/author/${writer.id}/`" class="authorLink">
            {{ writer.name }}
          </nuxt-link>
          <a v-if="writer.twitter" class="twitterLink" :href="writer.twitter">
            <img class="twitter" src="/images/icon_x.svg" alt="x" />
          </a>
        </dt>
        <dd class="text">{{ writer.text }}</dd>
      </dl>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    writer: {
      type: Object,
      required: true,
      default: () => ({}),
    },
  },
};
</script>

<style scoped>
.container {
  display: flex;
}

.content {
  flex: 1;
}

.name {
  display: flex;
  align-items: center;
  font-weight: bold;
  padding-bottom: 10px;
}

.twitterLink {
  display: block;
  height: 16px;
  margin-left: 10px;
}

.twitter {
  display: block;
  height: 16px;
}

.text {
  font-size: 14px;
}

.authorLink:hover {
  color: var(--color-purple);
  text-decoration: underline;
}

@media (min-width: 600px) {
  .wrapper {
    position: relative;
    border-top: 1px solid #666;
    margin-top: 80px;
    padding: 30px 20px 20px;
  }
  .title {
    position: absolute;
    top: 0;
    right: 20px;
    background-color: #2b2c30;
    color: #fff;
    font-size: 14px;
    padding: 2px 10px;
    border-radius: 0 0 5px 5px;
  }
  .image {
    border-radius: 50%;
    width: 100px;
    height: 100px;
    margin-right: 20px;
  }
}

@media (max-width: 600px) {
  .wrapper {
    position: relative;
    border-top: 1px solid #666;
    margin-top: 80px;
    padding: 30px 0px 20px;
  }
  .title {
    position: absolute;
    top: 0;
    right: 0;
    background-color: #2b2c30;
    color: #fff;
    font-size: 14px;
    padding: 2px 10px;
    border-radius: 0 0 5px 5px;
  }
  .image {
    border-radius: 50%;
    width: 60px;
    height: 60px;
    margin-right: 10px;
  }
}
</style>


================================================
FILE: functions/draft.js
================================================
const { client } = require('../utils/microcms');

// eslint-disable-next-line require-await
exports.handler = async (event) => {
  const { id, draftKey } = event.queryStringParameters;
  if (!id) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        error: 'Missing "id" query parameter',
      }),
    };
  }
  return client
    .get({
      endpoint: 'blog',
      contentId: id,
      queries: {
        draftKey,
        depth: 2,
      },
    })
    .then((data) => {
      return {
        statusCode: 200,
        body: JSON.stringify(data),
      };
    })
    .catch((error) => ({
      statusCode: 400,
      body: String(error),
    }));
};


================================================
FILE: functions/search.js
================================================
const { client } = require('../utils/microcms');

// eslint-disable-next-line require-await
exports.handler = async (event) => {
  const { q } = event.queryStringParameters;
  if (!q) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        error: 'Missing "q" query parameter',
      }),
    };
  }
  return client
    .get({
      endpoint: 'blog',
      queries: { q },
    })
    .then((data) => {
      return {
        statusCode: 200,
        body: JSON.stringify(data),
      };
    })
    .catch((error) => ({
      statusCode: 400,
      body: String(error),
    }));
};


================================================
FILE: layouts/README.md
================================================
# LAYOUTS

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your Application Layouts.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).


================================================
FILE: layouts/default.vue
================================================
<template>
  <div>
    <nuxt />
  </div>
</template>

<style>
@import '@/assets/styles/reset.css';
</style>


================================================
FILE: netlify.toml
================================================
[build]
  functions = "dist/api"

[[redirects]]
  from = "/usecase-cainz/"
  to = "https://microcms.io/interviews/cainz"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-web-creator-box/"
  to = "https://microcms.io/interviews/web-creator-box"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-fuller/"
  to = "https://microcms.io/interviews/fuller"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-dip/"
  to = "https://microcms.io/interviews/dip"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-gree-x/"
  to = "https://microcms.io/interviews/gree-x"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-chunichi/"
  to = "https://microcms.io/interviews/chunichi"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-ymj/"
  to = "https://microcms.io/interviews/ymj"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-kinto/"
  to = "https://microcms.io/interviews/kinto"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-nifty/"
  to = "https://microcms.io/interviews/nifty"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-cerezo/"
  to = "https://microcms.io/interviews/cerezo"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-scrap/"
  to = "https://microcms.io/interviews/scrap"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-freee/"
  to = "https://microcms.io/interviews/freee"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-tokyo-gas/"
  to = "https://microcms.io/interviews/tokyo-gas"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-odx/"
  to = "https://microcms.io/interviews/odx"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-alba/"
  to = "https://microcms.io/interviews/alba"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-otamanabi-no-mori/"
  to = "https://microcms.io/interviews/otamanabi-no-mori"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-elnet/"
  to = "https://microcms.io/interviews/elnet"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-smarthr/"
  to = "https://microcms.io/interviews/smarthr"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-leact/"
  to = "https://microcms.io/interviews/leact"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-dmm/"
  to = "https://microcms.io/interviews/dmm"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-housmart/"
  to = "https://microcms.io/interviews/housmart"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-gvatech/"
  to = "https://microcms.io/interviews/gvatech"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-toridoll/"
  to = "https://microcms.io/interviews/toridoll"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-polimill/"
  to = "https://microcms.io/interviews/polimill"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-reiwatravel/"
  to = "https://microcms.io/interviews/reiwatravel"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-ndc/"
  to = "https://microcms.io/interviews/ndc"
  status = 301
  force = true

[[redirects]]
from = "/usecase-yamap-store/"
to = "https://microcms.io/interviews/yamap-store"
status = 301
force = true

[[redirects]]
  from = "/usecase-nrinetcom/"
  to = "https://microcms.io/interviews/nrinetcom"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-discoveryjapan/"
  to = "https://microcms.io/interviews/discoveryjapan"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-paconsul/"
  to = "https://microcms.io/interviews/paconsul"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-chikaku/"
  to = "https://microcms.io/interviews/chikaku"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-zozotech/"
  to = "https://microcms.io/interviews/zozotech"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-sgis/"
  to = "https://microcms.io/interviews/sgis"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-monex-am/"
  to = "https://microcms.io/interviews/monex-am"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-dwango/"
  to = "https://microcms.io/interviews/dwango"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-beatfit/"
  to = "https://microcms.io/interviews/beatfit"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-odakyu/"
  to = "https://microcms.io/interviews/odakyu"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-medley/"
  to = "https://microcms.io/interviews/medley"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-port/"
  to = "https://microcms.io/interviews/port"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-acall/"
  to = "https://microcms.io/interviews/acall"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-knockonthedoor/"
  to = "https://microcms.io/interviews/knockonthedoor"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-mediba/"
  to = "https://microcms.io/interviews/mediba"
  status = 301
  force = true

[[redirects]]
  from = "/crowdworks-with-microcms/"
  to = "https://microcms.io/interviews/crowdworks-with-microcms"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-mediano/"
  to = "https://microcms.io/interviews/mediano"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-konicaminolta/"
  to = "https://microcms.io/interviews/konicaminolta"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-rebuild/"
  to = "https://microcms.io/interviews/rebuild"
  status = 301
  force = true

[[redirects]]
  from = "/usecase-interspace/"
  to = "https://microcms.io/interviews/interspace"
  status = 301
  force = true

[[redirects]]
  from = "/microcms-multilingual-site/"
  to = "https://help.microcms.io/ja/knowledge/multilingual-site"
  status = 301
  force = true

[[redirects]]
  from = "/*"
  to = "/404.html"
  status = 404


================================================
FILE: nuxt.config.js
================================================
import { client } from './utils/microcms';
const { API_KEY, SERVICE_ID, GTM_ID, FB_PIXEL_ID } = process.env;

export default {
  target: 'static',
  /*
   ** Headers of the page
   */
  head: {
    htmlAttrs: {
      prefix: 'og: http://ogp.me/ns#',
      lang: 'ja',
    },
    titleTemplate: '%s | microCMSブログ',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content:
          'microCMSはAPIベースの日本製ヘッドレスCMSです。本ブログはmicroCMSの開発メンバーがmicroCMSの使い方や技術的な内容を発信するブログです。',
      },
      {
        hid: 'og:site_name',
        property: 'og:site_name',
        content: 'microCMSブログ',
      },
      { hid: 'og:type', property: 'og:type', content: 'website' },
      {
        hid: 'og:url',
        property: 'og:url',
        content: 'https://blog.microcms.io',
      },
      { hid: 'og:title', property: 'og:title', content: 'microCMSブログ' },
      {
        hid: 'og:description',
        property: 'og:description',
        content:
          'microCMSはAPIベースの日本製ヘッドレスCMSです。本ブログはmicroCMSの開発メンバーがmicroCMSの使い方や技術的な内容を発信するブログです。',
      },
      {
        hid: 'og:image',
        property: 'og:image',
        content: 'https://blog.microcms.io/images/ogp.png',
      },

      { name: 'twitter:card', content: 'summary_large_image' },
      { name: 'twitter:site', content: '@micro_cms' },
    ],
    link: [
      {
        rel: 'icon',
        type: 'image/x-icon',
        href: 'https://blog.microcms.io/favicon.png',
      },
      {
        rel: 'alternate',
        type: 'application/atom+xml',
        href: 'https://blog.microcms.io/feed.xml',
        title: 'Atom',
      },
    ],
    script: [
      {
        src:
          'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.2.2/lazysizes.min.js',
        async: true,
      },
    ],
  },
  /*
   ** Customize the progress-bar color
   */
  loading: { color: '#331cbf' },
  /*
   ** Global CSS
   */
  css: [
    '@/assets/styles/reset.css',
    '@/assets/styles/colors.css',
    {
      src: '~/node_modules/highlight.js/styles/hybrid.css',
      lang: 'css',
    },
  ],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: ['~/plugins/vue-scrollto'],
  components: true,
  buildModules: ['@nuxtjs/eslint-module', '@nuxtjs/pwa'],
  /*
   ** Nuxt.js modules
   */
  modules: [
    ['@nuxtjs/dayjs'],
    GTM_ID ? ['@nuxtjs/gtm'] : undefined,
    FB_PIXEL_ID
      ? [
          'nuxt-facebook-pixel-module',
          {
            track: 'PageView',
            pixelId: FB_PIXEL_ID,
            autoPageView: true,
            disabled: false,
          },
        ]
      : undefined,
    ['@nuxtjs/sitemap'],
    '@nuxtjs/feed',
    '@nuxtjs/proxy',
    'nuxt-microcms-module',
  ].filter((v) => v),
  dayjs: {
    locales: ['ja'],
    defaultLocale: 'ja',
  },
  gtm: {
    id: GTM_ID || undefined,
  },
  proxy: ['http://localhost:9000/.netlify'],
  pwa: {
    workbox: {
      offlineAssets: [
        '/images/banner_logo.svg',
        '/images/icon_author.svg',
        '/images/icon_clock.svg',
        '/images/icon_facebook.svg',
        '/images/icon_discord.svg',
        '/images/icon_feed.svg',
        '/images/icon_hatena.svg',
        '/images/icon_menu.svg',
        '/images/icon_quote.svg',
        '/images/icon_search.svg',
        '/images/icon_link.svg',
        '/images/logo.svg',
      ],
      runtimeCaching: [
        {
          urlPattern: 'https://images.microcms-assets.io/.*',
          handler: 'staleWhileRevalidate',
        },
      ],
    },
  },
  microcms: {
    options: {
      serviceDomain: SERVICE_ID,
      apiKey: API_KEY,
    },
    mode: process.env.NODE_ENV === 'production' ? 'server' : 'all',
  },
  /*
   ** Build configuration
   */
  build: {
    postcss: {
      postcssOptions: {
        plugins: {
          'postcss-nested': {},
        },
      },
    },
    extend(config, ctx) {
      // Run ESLint on save
      if (ctx.isDev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/,
        });
      }
    },
  },
  router: {
    extendRoutes(routes, resolve) {
      routes.push({
        path: '/page/:id',
        component: resolve(__dirname, 'pages/index.vue'),
        name: 'pages',
      });
      routes.push({
        path: '/category/:categoryId/page/:id',
        component: resolve(__dirname, 'pages/index.vue'),
        name: 'categories',
      });
      routes.push({
        path: '/tag/:tagId/page/:id',
        component: resolve(__dirname, 'pages/index.vue'),
        name: 'tags',
      });
      routes.push({
        path: '/author/:authorId/page/:id',
        component: resolve(__dirname, 'pages/author/_authorId.vue'),
        name: 'authors',
      });
      routes.push({
        path: '*',
        component: resolve(__dirname, 'pages/404.vue'),
        name: 'custom',
      });
    },
  },
  generate: {
    interval: 200,
    async routes() {
      const range = (start, end) =>
        [...Array(end - start + 1)].map((_, i) => start + i);
      const limit = 25;
      const popularArticles = (
        await client.get({
          endpoint: 'popular-articles',
        })
      ).articles;
      const banners = await client.get({
        endpoint: 'banners',
      });
      const ctaContents = (
        await client.get({
          endpoint: 'cta',
        })
      ).contents;

      // 詳細ページ
      const getArticles = (offset = 0) => {
        return client
          .get({
            endpoint: 'blog',
            queries: {
              offset,
              limit,
              depth: 2,
            },
          })
          .then(async (res) => {
            let articles = [];
            if (res.totalCount > offset + limit) {
              articles = await getArticles(offset + limit);
            }
            return [
              ...res.contents.map((content) => ({
                route: `/${content.id}`,
                payload: { content, popularArticles, banners, ctaContents },
              })),
              ...articles,
            ];
          });
      };
      const articles = await getArticles();

      // 一覧ページ
      const index = {
        route: '/',
        payload: { popularArticles, banners },
      };

      // 一覧のページング
      const pages = await client
        .get({
          endpoint: 'blog',
          queries: {
            limit: 0,
          },
        })
        .then((res) =>
          range(1, Math.ceil(res.totalCount / 10)).map((p) => ({
            route: `/page/${p}`,
            payload: { popularArticles, banners },
          }))
        );

      // 検索ページ
      const search = {
        route: '/search',
        payload: { popularArticles, banners },
      };

      const categories = await client
        .get({
          endpoint: 'categories',
          queries: {
            fields: 'id',
          },
        })
        .then(({ contents }) => {
          return contents.map((content) => content.id);
        });

      // カテゴリーページ
      const categoryPages = await Promise.all(
        categories.map((category) =>
          client
            .get({
              endpoint: 'blog',
              queries: {
                limit: 0,
                filters: `category[equals]${category}`,
              },
            })
            .then((res) => {
              return range(1, Math.ceil(res.totalCount / 10)).map((p) => ({
                route: `/category/${category}/page/${p}`,
                payload: { popularArticles, banners },
              }));
            })
        )
      );
      const flattenCategoryPages = [].concat.apply([], categoryPages);

      const tags = await client
        .get({
          endpoint: 'tags',
          queries: {
            fields: 'id',
            limit: 100,
          },
        })
        .then(({ contents }) => {
          return contents.map((content) => content.id);
        });

      // タグページ
      const tagPages = await Promise.all(
        tags.map((tag) =>
          client
            .get({
              endpoint: 'blog',
              queries: {
                limit: 0,
                filters: `tag[contains]${tag}`,
              },
            })
            .then((res) => {
              return range(1, Math.ceil(res.totalCount / 10)).map((p) => ({
                route: `/tag/${tag}/page/${p}`,
                payload: { popularArticles, banners },
              }));
            })
        )
      );
      const flattenTagPages = [].concat.apply([], tagPages);

      return [
        index,
        search,
        ...articles,
        ...pages,
        ...flattenCategoryPages,
        ...flattenTagPages,
      ];
    },
    dir: 'dist',
  },
  sitemap: {
    path: '/sitemap.xml',
    hostname: 'https://blog.microcms.io',
    exclude: ['/draft', '/404'],
    gzip: true,
    trailingSlash: true,
  },
  feed: async () => {
    const authors = await client
      .get({
        endpoint: 'authors',
        queries: {
          limit: 100,
        },
      })
      .then((res) => res.contents);
    const authorsSettings = authors.map((author) => {
      return {
        path: `/author/${author.id}/feed.xml`,
        async create(feed) {
          feed.options = {
            title: `${author.name}が執筆した記事 | microCMSブログ`,
            link: 'https://blog.microcms.io/feed.xml',
            description:
              'microCMSはAPIベースの日本製ヘッドレスCMSです。本ブログはmicroCMSの開発メンバーがmicroCMSの使い方や技術的な内容を発信するブログです。',
          };
          const posts = await client
            .get({
              endpoint: 'blog',
              queries: {
                filters: `writer[equals]${author.id}`,
              },
            })
            .then((res) => res.contents);
          posts.forEach((post) => {
            feed.addItem({
              title: post.title,
              id: post.id,
              link: `https://blog.microcms.io/${post.id}/`,
              description: post.description,
              content: post.description,
              date: new Date(post.publishedAt || post.createdAt),
              image: post.ogimage && post.ogimage.url,
            });
          });
        },
        cacheTime: 1000 * 60 * 15,
        type: 'rss2',
      };
    });
    return [
      {
        path: '/feed.xml',
        async create(feed) {
          feed.options = {
            title: 'microCMSブログ',
            link: 'https://blog.microcms.io/feed.xml',
            description:
              'microCMSはAPIベースの日本製ヘッドレスCMSです。本ブログはmicroCMSの開発メンバーがmicroCMSの使い方や技術的な内容を発信するブログです。',
          };

          const posts = await client
            .get({
              endpoint: 'blog',
            })
            .then((res) => res.contents);

          posts.forEach((post) => {
            feed.addItem({
              title: post.title,
              id: post.id,
              link: `https://blog.microcms.io/${post.id}/`,
              description: post.description,
              content: post.description,
              date: new Date(post.publishedAt || post.createdAt),
              image: post.ogimage && post.ogimage.url,
            });
          });
        },
        cacheTime: 1000 * 60 * 15,
        type: 'rss2',
      },
      {
        path: '/feed_update.xml',
        async create(feed) {
          feed.options = {
            title: '更新情報|microCMSブログ',
            link: 'https://blog.microcms.io/feed.xml',
            description:
              'microCMSはAPIベースの日本製ヘッドレスCMSです。本ブログはmicroCMSの開発メンバーがmicroCMSの使い方や技術的な内容を発信するブログです。',
          };

          const posts = await client
            .get({
              endpoint: 'blog',
              queries: {
                filters: 'category[equals]update',
              },
            })
            .then((res) => res.contents);

          posts.forEach((post) => {
            feed.addItem({
              title: post.title,
              id: post.id,
              link: `https://blog.microcms.io/${post.id}/`,
              description: post.description,
              content: post.description,
              date: new Date(post.publishedAt || post.createdAt),
              image: post.ogimage && post.ogimage.url,
            });
          });
        },
        cacheTime: 1000 * 60 * 15,
        type: 'rss2',
      },
      {
        path: '/feed_usecase.xml',
        async create(feed) {
          feed.options = {
            title: '導入事例|microCMSブログ',
            link: 'https://blog.microcms.io/feed.xml',
            description:
              'microCMSはAPIベースの日本製ヘッドレスCMSです。本ブログはmicroCMSの開発メンバーがmicroCMSの使い方や技術的な内容を発信するブログです。',
          };

          const posts = await client
            .get({
              endpoint: 'blog',
              queries: {
                filters: 'category[equals]usecase',
              },
            })
            .then((res) => res.contents);

          posts.forEach((post) => {
            feed.addItem({
              title: post.title,
              id: post.id,
              link: `https://blog.microcms.io/${post.id}/`,
              description: post.description,
              content: post.description,
              date: new Date(post.publishedAt || post.createdAt),
              image: post.ogimage && post.ogimage.url,
            });
          });
        },
        cacheTime: 1000 * 60 * 15,
        type: 'rss2',
      },
      ...authorsSettings,
    ];
  },
};


================================================
FILE: package.json
================================================
{
  "name": "microcms-blog",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "nuxt",
    "generate": "nuxt generate --fail-on-error",
    "start": "nuxt start",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "lintfix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
    "functions:build": "netlify-lambda build functions",
    "functions:serve": "netlify-lambda serve functions"
  },
  "dependencies": {
    "axios": "^0.21.2",
    "cheerio": "^1.0.0-rc.3",
    "encoding": "^0.1.13",
    "highlight.js": "^10.0.0",
    "microcms-js-sdk": "^2.0.0",
    "nuxt": "^2.17.2",
    "nuxt-facebook-pixel-module": "^1.5.0",
    "nuxt-microcms-module": "^1.0.1"
  },
  "devDependencies": {
    "@nuxtjs/dayjs": "^1.1.9",
    "@nuxtjs/dotenv": "^1.4.1",
    "@nuxtjs/eslint-config": "^3.0.0",
    "@nuxtjs/eslint-module": "^2.0.0",
    "@nuxtjs/feed": "^1.1.0",
    "@nuxtjs/google-analytics": "^2.2.0",
    "@nuxtjs/gtm": "^2.4.0",
    "@nuxtjs/proxy": "^2.1.0",
    "@nuxtjs/pwa": "^3.0.0-beta.20",
    "@nuxtjs/sitemap": "^2.4.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^7.2.0",
    "eslint-config-prettier": "^6.11.0",
    "eslint-plugin-nuxt": "^1.0.0",
    "eslint-plugin-prettier": "^3.1.4",
    "netlify-lambda": "^1.6.3",
    "postcss-css-variables": "^0.18.0",
    "postcss-import": "^12.0.1",
    "postcss-nested": "^4.1.2",
    "prettier": "^2.0.5",
    "vue-scrollto": "^2.17.1"
  }
}


================================================
FILE: pages/404.vue
================================================
<template>
  <div class="wrapper">
    <Header />
    <div class="container">
      <dl>
        <dt class="status">404</dt>
        <dd class="message">ページが見つかりません</dd>
      </dl>
    </div>
    <Footer />
  </div>
</template>

<script>
export default {
  head() {
    return {
      titleTemplate: null,
      title: 'ページが見つかりません | microCMSブログ',
    };
  },
};
</script>

<style scoped>
.wrapper {
  display: flex;
  flex-direction: column;
  height: 100vh;
}
.container {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  margin: 40px 0;
}
.status {
  font-size: 120px;
  font-weight: bold;
  color: #999;
}
.message {
  color: #999;
  font-size: 20px;
}
</style>


================================================
FILE: pages/README.md
================================================
# PAGES

This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).


================================================
FILE: pages/_slug/index.vue
================================================
<template>
  <div class="wrapper">
    <Header />
    <div class="divider">
      <article class="article">
        <div class="ogimageWrap">
          <picture v-if="ogimage">
            <source
              media="(min-width: 1160px)"
              type="image/webp"
              :srcset="`${ogimage.url}?w=820&fm=webp, ${ogimage.url}?w=1640&fm=webp 2x`"
            />
            <source
              media="(min-width: 820px)"
              type="image/webp"
              :srcset="`${ogimage.url}?w=740&fm=webp, ${ogimage.url}?w=1480&fm=webp 2x`"
            />
            <source
              media="(min-width: 768px)"
              type="image/webp"
              :srcset="`${ogimage.url}?w=728&fm=webp, ${ogimage.url}?w=1456&fm=webp 2x`"
            />
            <source
              media="(max-width: 768px)"
              type="image/webp"
              :srcset="`${ogimage.url}?w=375&fm=webp, ${ogimage.url}?w=750&fm=webp 2x`"
            />
            <img
              ref="ogimage"
              :src="ogimage.url + '?w=820&q=100'"
              class="ogimage"
              alt
            />
          </picture>
          <picture v-else>
            <source type="image/webp" :srcset="defaultOgimage" />
            <img ref="ogimage" :src="defaultOgimage" class="ogimage" alt />
          </picture>
        </div>
        <Breadcrumb :category="category" />
        <div class="main">
          <Share :id="id" :title="title" />
          <div class="container">
            <h1 class="title">{{ title }}</h1>
            <Meta
              :created-at="publishedAt || createdAt"
              :revised-at="revisedAt"
              :author="writer"
              :category="category"
              :tags="tag"
              :is-single-page="true"
            />
            <Alert
              :is-old-post="isOldPost"
              :is-technical-post="isTechnicalPost"
            />
            <Toc :id="id" :toc="toc" :visible="toc_visible" />
            <Post :body="body" />
            <Cta :id="id" :contents="ctaContents" />
            <ConversionPoint
              :id="id"
              :contents="cv_point"
              :theme="
                cv_point === null || cv_point.length <= 0 ? '' : 'thumbnail'
              "
            />
            <NextBlogNavigation :previous="previous_blog" :next="next_blog" />
            <ShareButtons :id="id" :title="title" />
            <Writer v-if="writer" :writer="writer" />
            <Partner v-if="partner" :partner="partner" />
            <RelatedBlogs
              v-if="related_blogs.length > 0"
              :blogs="related_blogs"
            />
          </div>
        </div>
      </article>
      <aside class="aside">
        <Search />
        <Categories :categories="categories" />
        <Tags :tags="tags" />
        <PopularArticles :contents="popularArticles" />
        <div class="followArea">
          <Banners :id="`blog-${id}`" :banners="banners" />
          <Latest :contents="contents" />
        </div>
      </aside>
    </div>
    <Footer />
  </div>
</template>

<script>
import cheerio from 'cheerio';
import hljs from 'highlight.js';
import getDefaultOgimage from '../../utils/getDefaultOgimage';

export default {
  async asyncData({ params, payload, $microcms }) {
    const data =
      payload !== undefined
        ? payload.content
        : await $microcms.get({
            endpoint: 'blog',
            contentId: params.slug,
            queries: {
              depth: 2,
            },
          });
    const popularArticles =
      payload !== undefined && payload.popularArticles !== undefined
        ? payload.popularArticles
        : (
            await $microcms.get({
              endpoint: 'popular-articles',
            })
          ).articles;
    const banners =
      payload !== undefined
        ? payload.banners
        : await $microcms.get({
            endpoint: 'banners',
          });
    const ctaContents =
      payload !== undefined && payload.ctaContents !== undefined
        ? payload.ctaContents
        : (
            await $microcms.get({
              endpoint: 'cta',
            })
          ).contents;
    const { contents } = await $microcms.get({
      endpoint: 'blog',
    });
    const categories = await $microcms.get({
      endpoint: 'categories',
      queries: {
        limit: 100,
      },
    });
    const tags = await $microcms.get({
      endpoint: 'tags',
      queries: {
        limit: 100,
      },
    });
    const $ = cheerio.load(data.body);
    const headings = $('h1, h2, h3').toArray();
    const toc = headings.map((d) => {
      return {
        text: d.children[0].data,
        id: d.attribs.id,
        name: d.name,
      };
    });
    $('pre code').each((_, elm) => {
      const res = hljs.highlightAuto($(elm).text());
      $(elm).html(res.value);
      $(elm).addClass('hljs');
    });
    $('img').each((_, elm) => {
      $(elm).attr('class', 'lazyload');
      $(elm).attr('data-src', elm.attribs.src);
      $(elm).removeAttr('src');
    });
    $('a').each((_, elm) => {
      // hrefが動画リンクのものはvideoタグを作成し、srcにhrefの値を設定
      if ($(elm).attr('href').endsWith('.mp4')) {
        const video = $('<video></video>');
        video.attr('src', $(elm).attr('href'));
        video.attr('controls', 'controls');
        video.attr('preload', 'metadata');
        $(elm).replaceWith(video);
      }
    });

    data.related_blogs.forEach((blog) => {
      blog.defaultOgimage = getDefaultOgimage(blog);
    });

    return {
      ...data,
      defaultOgimage: getDefaultOgimage(data),
      popularArticles,
      banners,
      ctaContents,
      body: $.html(),
      toc,
      categories: categories.contents,
      tags: tags.contents,
      contents,
    };
  },
  data() {
    return {
      publishedAt: '',
      ogimage: null,
    };
  },
  computed: {
    isOldPost() {
      const currentDate = new Date();

      const oneYearAgoDate = new Date();
      oneYearAgoDate.setFullYear(currentDate.getFullYear() - 1);

      const createdAtDate = new Date(
        this.revisedAt || this.publishedAt || this.createdAt
      );

      // createdAtが1年前の日付かどうか判定
      return createdAtDate < oneYearAgoDate;
    },
    isTechnicalPost() {
      // エンジニアリング、チュートリアル、更新情報カテゴリが古い記事チェックの対象
      return (
        this.category.id === 'engineering' ||
        this.category.id === 'tutorial' ||
        this.category.id === 'update'
      );
    },
  },
  head() {
    return {
      title: this.title,
      meta: [
        { hid: 'description', name: 'description', content: this.description },
        { hid: 'og:title', property: 'og:title', content: this.title },
        {
          hid: 'og:description',
          property: 'og:description',
          content: this.description,
        },
        {
          hid: 'og:url',
          property: 'og:url',
          content: `https://blog.microcms.io/${this.id}/`,
        },
        {
          hid: 'og:image',
          property: 'og:image',
          content: this.ogimage ? this.ogimage.url : this.defaultOgimage,
        },
      ],
    };
  },
};
</script>

<style scoped>
.category {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 3px;
  color: #fff;
  margin-top: 10px;
  font-size: 14px;
  font-weight: bold;
}

@media (min-width: 1160px) {
  .wrapper {
    position: relative;
  }

  .divider {
    display: flex;
    justify-content: space-between;
    width: 1160px;
    margin: 20px auto 0;
  }

  .article {
    width: 820px;
  }

  .aside {
    width: 300px;
  }

  .main {
    display: flex;
    justify-content: space-between;
    margin-top: 40px;
  }

  .followArea {
    position: sticky !important;
    top: 115px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    border-radius: 5px;
  }

  .ogimage {
    display: block;
    width: 100%;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    margin-left: 80px;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 40px;
    color: #2b2c30;
  }
}

@media (min-width: 820px) and (max-width: 1160px) {
  .wrapper {
    position: relative;
    margin-top: 40px;
  }

  .divider {
    margin: 20px auto 0;
    width: 740px;
  }

  .article {
    width: 740px;
  }

  .aside {
    margin-top: 60px;
    margin-left: 104px;
  }

  .main {
    display: flex;
    justify-content: space-between;
    margin-top: 40px;
    align-items: strech;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    border-radius: 5px;
  }

  .ogimage {
    display: block;
    width: 100%;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    margin-left: 80px;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 40px;
    color: #2b2c30;
  }

  .meta {
    padding: 10px 0 40px;
  }

  .timestamp {
    display: inline-flex;
    align-items: center;
    color: #919299;
    margin-right: 20px;

    img {
      margin-right: 6px;
    }
  }

  .author {
    display: inline-flex;
    align-items: center;
    color: #919299;

    img {
      margin-right: 6px;
    }
  }
}
@media (min-width: 600px) and (max-width: 820px) {
  .wrapper {
    position: relative;
    margin-top: 40px;
  }

  .divider {
    margin: 20px 0 0;
    padding: 0 20px;
  }

  .article {
    width: 100%;
  }

  .aside {
    margin-top: 60px;
    margin-left: 104px;
  }

  .main {
    display: flex;
    justify-content: space-between;
    margin-top: 40px;
    align-items: strech;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    border-radius: 5px;
  }

  .ogimage {
    display: block;
    width: 100%;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    margin-left: 80px;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 40px;
    color: #2b2c30;
  }
}
@media (max-width: 600px) {
  .wrapper {
    position: relative;
  }

  .divider {
    padding: 0 16px;
  }

  .article {
    width: 100%;
  }

  .aside {
    margin-top: 60px;
    width: 100%;
  }

  .breadcrumb {
    display: flex;
    flex-wrap: wrap;
    padding-top: 20px;
    margin-bottom: -20px;
  }

  .breadcrumbList {
    color: #616269;
    font-size: 14px;

    a {
      color: #331cbf;
    }

    &::after {
      content: '>';
      margin: 0 10px;
    }

    &:last-child&::after {
      content: '';
      margin: 0;
    }
  }

  .main {
    display: flex;
    flex-direction: column-reverse;
    margin-top: 30px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    height: 270px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    width: calc(100% + 32px);
    margin: 0 -16px;
  }

  .ogimage {
    display: block;
    width: 100%;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 24px;
    color: #2b2c30;
  }
}
</style>


================================================
FILE: pages/author/_authorId.vue
================================================
<template>
  <div id="author" class="wrapper">
    <Header />

    <div class="profile">
      <div class="inner">
        <picture v-if="author.image">
          <source
            type="image/webp"
            :data-srcset="author.image.url + '?w=360&h=360&fm=webp'"
          />
          <img
            :data-src="author.image.url + '?w=360&h=360'"
            :width="180"
            :height="180"
            class="authorImg lazyload"
            alt
          />
        </picture>
        <dl class="content">
          <dt class="nameWrapper">
            <span class="name">{{ author.name }}</span>
            <a v-if="author.twitter" :href="author.twitter" target="twitter">
              <img
                src="/images/icon_x.svg"
                alt="X"
                class="icon lazyload"
                width="32"
                height="32"
              />
            </a>
            <a v-if="author.facebook" :href="author.facebook" target="facebook">
              <img
                src="/images/icon_facebook.svg"
                alt="Facebook"
                class="icon lazyload"
                width="32"
                height="32"
              />
            </a>
            <a v-if="author.github" :href="author.github" target="github">
              <img
                src="/images/icon_github.svg"
                alt="GitHub"
                class="icon lazyload"
                width="32"
                height="32"
              />
            </a>
            <a :href="`/author/${author.id}/feed.xml`" target="rss">
              <img
                src="/images/icon_feed.svg"
                alt="RSS"
                class="icon lazyload"
                width="32"
                height="32"
              />
            </a>
          </dt>
          <dd class="text">
            {{ author.text }}
          </dd>
        </dl>
        <div class="post">
          <span class="number">{{ totalCount }}</span
          ><span>Posts</span>
        </div>
      </div>
    </div>

    <div class="divider">
      <div class="container">
        <Breadcrumb />
        <h2 class="title">{{ author.name }}が執筆した記事</h2>
        <div v-show="contents.length === 0" class="loader">
          記事がありません
        </div>
        <ul class="lists">
          <li v-for="content in contents" :key="content.id" class="list">
            <Card :content="content" :author="author" />
          </li>
        </ul>
        <Pagination
          v-if="contents.length > 0"
          :pager="pager"
          :current="Number(page)"
          :author="author"
        />
      </div>
      <aside class="aside">
        <Banners id="list" :banners="banners" />
        <Search />
        <Categories :categories="categories" />
        <Tags :tags="tags" />
        <PopularArticles :contents="popularArticles" />
      </aside>
    </div>
    <Footer />
  </div>
</template>

<script>
import getDefaultOgimage from '../../utils/getDefaultOgimage';

export default {
  async asyncData({ params, payload, $microcms }) {
    const page = params.id || '1';
    const authorId = params.authorId;
    const limit = 10;
    const popularArticles =
      payload !== undefined && payload.popularArticles !== undefined
        ? payload.popularArticles
        : (
            await $microcms.get({
              endpoint: 'popular-articles',
            })
          ).articles;
    const banners =
      payload !== undefined
        ? payload.banners
        : await $microcms.get({
            endpoint: 'banners',
          });
    const author = await $microcms.get({
      endpoint: `authors/${authorId}`,
    });
    const data = await $microcms.get({
      endpoint: 'blog',
      queries: {
        limit,
        offset: (page - 1) * limit,
        filters: `writer[equals]${authorId}`,
      },
    });
    const categories = await $microcms.get({
      endpoint: 'categories',
      queries: {
        limit: 100,
      },
    });
    const tags = await $microcms.get({
      endpoint: 'tags',
      queries: {
        limit: 100,
      },
    });

    data.contents.forEach((content) => {
      content.defaultOgimage = getDefaultOgimage(content);
    });

    return {
      ...data,
      author,
      categories: categories.contents,
      tags: tags.contents,
      authorId,
      popularArticles,
      banners,
      page,
      pager: [...Array(Math.ceil(data.totalCount / limit)).keys()],
    };
  },
  data() {
    return {
      contents: this.contents || [],
      totalCount: this.totalCount || 0,
      pager: this.pager || [],
      loading: true,
    };
  },
  head() {
    return {
      titleTemplate: null,
      title: `${this.author.name}が執筆した記事 | microCMSブログ`,
    };
  },
};
</script>

<style scoped>
.profile {
  background-color: var(--color-bg-purple-lightest);
  padding: 64px 0;
  margin: -45px auto 64px;
}
.inner {
  max-width: 1160px;
  margin: 0 auto;
  display: flex;
}
.content {
  margin-left: 60px;
}
.nameWrapper {
  display: flex;
  align-items: center;
  margin-bottom: 16px;
}
.name {
  font-size: 32px;
  font-weight: 900;
  margin-right: 24px;
}
.text {
  margin-top: 8px;
}
.icon {
  margin-right: 16px;
}
.authorImg {
  border-radius: 50%;
}
.post {
  text-align: center;
  font-weight: 800;
  font-size: 24px;
  margin-left: 60px;
}
.number {
  display: block;
  font-size: 72px;
  margin-bottom: -10px;
}
.lists {
  display: flex;
  flex-wrap: wrap;
}
.title {
  font-size: 24px;
  font-weight: bold;
  margin: 16px 0 24px;
}
.list {
  width: 340px;
  padding: 0 0 64px;
}
.loader {
  color: var(--color-text-disabled);
  font-size: 20px;
  text-align: center;
  padding: 150px;
}

@media (min-width: 1160px) {
  .divider {
    display: flex;
    justify-content: space-between;
    width: 1160px;
    margin: 20px auto 0;
  }

  .container {
    width: 820px;
  }

  .aside {
    width: 300px;
  }
  .link {
    display: flex;
    justify-content: space-between;
  }
  .list:nth-child(odd) {
    margin-right: 60px;
  }
}
@media (min-width: 820px) and (max-width: 1160px) {
  .profile {
    margin: 0 auto 64px;
  }
  .inner {
    max-width: 740px;
    display: block;
    text-align: center;
  }
  .post,
  .content {
    margin: 16px 0;
  }
  .divider {
    margin: 20px auto 0;
    width: 740px;
  }
  .aside {
    margin-top: 60px;
  }
  .nameWrapper {
    justify-content: center;
  }

  .list:nth-child(odd) {
    margin-right: 60px;
  }
}
@media (max-width: 820px) {
  .lists {
    width: 100%;
  }
  .profile {
    margin: 0 auto 64px;
    padding: 64px 20px;
  }
  .inner {
    max-width: 740px;
    display: block;
    text-align: center;
  }
  .content,
  .post {
    margin: 16px 0;
  }
  .divider {
    margin: 20px 0 0;
    padding: 0 20px;
  }

  .aside {
    margin-top: 60px;
    width: 100%;
  }
  .nameWrapper {
    display: block;
  }
  .name {
    display: block;
    margin-right: 0;
    margin-bottom: 16px;
  }
  .list {
    width: 100%;
    padding: 32px 0;
    border-bottom: 1px solid var(--color-border-light);

    &:first-child {
      padding-top: 16px;
    }
  }
  .loader {
    color: var(--color-text-disabled);
    font-size: 16px;
    padding-top: 20px;
  }
}
</style>


================================================
FILE: pages/draft/index.vue
================================================
<template>
  <div class="wrapper">
    <Header />
    <div class="divider">
      <p v-if="!data.id" class="loading">Now Loading...</p>
      <article v-if="data.id" class="article">
        <div class="ogimageWrap">
          <picture v-if="data.ogimage">
            <source
              media="(min-width: 1160px)"
              type="image/webp"
              :srcset="`${data.ogimage.url}?w=820&fm=webp, ${data.ogimage.url}?w=1640&fm=webp 2x`"
            />
            <source
              media="(min-width: 820px)"
              type="image/webp"
              :srcset="`${data.ogimage.url}?w=740&fm=webp, ${data.ogimage.url}?w=1480&fm=webp 2x`"
            />
            <source
              media="(min-width: 768px)"
              type="image/webp"
              :srcset="`${data.ogimage.url}?w=728&fm=webp, ${data.ogimage.url}?w=1456&fm=webp 2x`"
            />
            <source
              media="(max-width: 768px)"
              type="image/webp"
              :srcset="`${data.ogimage.url}?w=375&fm=webp, ${data.ogimage.url}?w=750&fm=webp 2x`"
            />
            <img
              ref="ogimage"
              :src="data.ogimage.url + '?w=820&q=100'"
              class="ogimage"
              alt
            />
          </picture>
          <picture v-else>
            <source type="image/webp" :srcset="data.defaultOgimage" />
            <img ref="ogimage" :src="data.defaultOgimage" class="ogimage" alt />
          </picture>
        </div>
        <Breadcrumb :category="data.category" />
        <div class="main">
          <Share :id="data.id" :title="data.title" />
          <div class="container">
            <h1 class="title">{{ data.title }}</h1>
            <Meta
              :created-at="data.publishedAt || data.createdAt"
              :author="data.writer"
              :category="data.category"
              :tags="data.tag"
              :is-single-page="true"
            />
            <Toc :id="data.id" :toc="toc" :visible="data.toc_visible" />
            <Post :body="data.body" />
            <ConversionPoint
              :id="data.id"
              :contents="data.cv_point"
              :theme="
                data.cv_point === null || data.cv_point.length <= 0
                  ? ''
                  : 'thumbnail'
              "
            />
            <Writer v-if="data.writer" :writer="data.writer" />
            <Partner v-if="data.partner" :partner="data.partner" />
            <RelatedBlogs
              v-if="data.related_blogs.length > 0"
              :blogs="data.related_blogs"
            />
          </div>
        </div>
      </article>
      <aside class="aside">
        <Search />
        <Categories :categories="categories" />
        <Tags :tags="tags" />
        <div class="followArea">
          <Banners :id="`draft-${data.id}`" :banners="banners" />
          <Latest :contents="contents" />
        </div>
      </aside>
    </div>
    <Footer />
  </div>
</template>

<script>
import axios from 'axios';
import cheerio from 'cheerio';
import hljs from 'highlight.js';
import getDefaultOgimage from '../../utils/getDefaultOgimage';

export default {
  async asyncData({ $microcms }) {
    const categories = await $microcms.get({
      endpoint: 'categories',
      queries: {
        limit: 100,
      },
    });
    const tags = await $microcms.get({
      endpoint: 'tags',
      queries: {
        limit: 100,
      },
    });
    const banners = await $microcms.get({
      endpoint: 'banners',
    });
    const { contents } = await $microcms.get({
      endpoint: 'blog',
    });
    return {
      categories: categories.contents,
      tags: tags.contents,
      banners,
      contents,
    };
  },
  data() {
    return {
      data: {
        id: '',
        ogimage: {
          url: '',
        },
        body: '',
        title: '',
        createdAt: '',
        publishedAt: '',
        toc_visible: false,
        writer: {
          id: '',
          name: '',
          image: {
            url: '',
          },
          text: '',
        },
        partner: {
          id: '',
          company: '',
          url: '',
          description: '',
          logo: {
            url: '',
          },
        },
        category: {
          name: '',
          color: '',
        },
        related_blogs: [],
      },
      toc: [],
      contents: [],
      categories: [],
    };
  },
  async created() {
    const query = this.$route.query;
    if (query.id === undefined || query.draftKey === undefined) {
      return;
    }
    const { data, error } = await axios
      .get(
        `/.netlify/functions/draft?id=${query.id}&draftKey=${query.draftKey}`
      )
      .catch((error) => ({ error }));
    if (error) {
      return;
    }
    this.data = data;

    // 目次作成
    const $ = cheerio.load(data.body);
    const headings = $('h1, h2, h3').toArray();
    const toc = headings.map((d) => {
      return {
        text: d.children[0].data,
        id: d.attribs.id,
        name: d.name,
      };
    });
    this.toc = toc;
    $('pre code').each((_, elm) => {
      const res = hljs.highlightAuto($(elm).text());
      $(elm).html(res.value);
      $(elm).addClass('hljs');
    });
    $('a').each((_, elm) => {
      // hrefが動画リンクのものはvideoタグを作成し、srcにhrefの値を設定
      if ($(elm).attr('href').endsWith('.mp4')) {
        const video = $('<video></video>');
        video.attr('src', $(elm).attr('href'));
        video.attr('controls', 'controls');
        video.attr('preload', 'metadata');
        $(elm).replaceWith(video);
      }
    });
    this.data.body = $.html();
    this.data.defaultOgimage = getDefaultOgimage(this.data);
    this.data.related_blogs.forEach((blog) => {
      blog.defaultOgimage = getDefaultOgimage(blog);
    });
  },
  head() {
    return {
      title: this.title,
      meta: [
        {
          hid: 'description',
          name: 'description',
          content: this.data && this.data.description,
        },
        {
          hid: 'og:title',
          property: 'og:title',
          content: this.data && this.data.title,
        },
        {
          hid: 'og:description',
          property: 'og:description',
          content: this.data && this.data.description,
        },
        {
          hid: 'og:url',
          property: 'og:url',
          content: `https://blog.microcms.io/${this.data && this.data.id}`,
        },
        {
          hid: 'og:image',
          property: 'og:image',
          content: this.data && this.data.ogimage && this.data.ogimage.url,
        },
      ],
      link: [
        {
          rel: 'stylesheet',
          href:
            'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/monokai-sublime.min.css',
        },
      ],
      script: [
        {
          src:
            'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js',
          async: true,
        },
      ],
    };
  },
};
</script>

<style scoped>
.category {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 3px;
  color: #fff;
  margin-top: 10px;
  font-size: 14px;
  font-weight: bold;
}

.loading {
  color: var(--color-text-off);
}

@media (min-width: 1160px) {
  .wrapper {
    position: relative;
  }

  .divider {
    display: flex;
    justify-content: space-between;
    width: 1160px;
    margin: 20px auto 0;
  }

  .article {
    width: 820px;
  }

  .aside {
    width: 300px;
  }

  .main {
    display: flex;
    justify-content: space-between;
    margin-top: 40px;
  }

  .followArea {
    position: sticky !important;
    top: 115px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    border-radius: 5px;
  }

  .ogimage {
    display: block;
    width: 100%;
    transition: transform 0.5s ease, opacity 0.5s ease;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    margin-left: 80px;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 40px;
    color: #2b2c30;
  }
}

@media (min-width: 820px) and (max-width: 1160px) {
  .wrapper {
    position: relative;
    margin-top: 40px;
  }

  .divider {
    margin: 20px auto 0;
    width: 740px;
  }

  .article {
    width: 740px;
  }

  .aside {
    margin-top: 60px;
    margin-left: 104px;
  }

  .main {
    display: flex;
    justify-content: space-between;
    margin-top: 40px;
    align-items: strech;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    border-radius: 5px;
  }

  .ogimage {
    display: block;
    width: 100%;
    transition: transform 0.5s ease;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    margin-left: 80px;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 40px;
    color: #2b2c30;
  }

  .meta {
    padding: 10px 0 40px;
  }

  .timestamp {
    display: inline-flex;
    align-items: center;
    color: #919299;
    margin-right: 20px;

    img {
      margin-right: 6px;
    }
  }

  .author {
    display: inline-flex;
    align-items: center;
    color: #919299;

    img {
      margin-right: 6px;
    }
  }
}
@media (min-width: 600px) and (max-width: 820px) {
  .wrapper {
    position: relative;
    margin-top: 40px;
  }

  .divider {
    margin: 20px 0 0;
    padding: 0 20px;
  }

  .article {
    width: 100%;
  }

  .aside {
    margin-top: 60px;
    margin-left: 104px;
  }

  .main {
    display: flex;
    justify-content: space-between;
    margin-top: 40px;
    align-items: strech;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    border-radius: 5px;
  }

  .ogimage {
    display: block;
    width: 100%;
    transition: transform 0.5s ease;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    margin-left: 80px;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 40px;
    color: #2b2c30;
  }
}
@media (max-width: 600px) {
  .wrapper {
    position: relative;
  }

  .divider {
    padding: 0 16px;
  }

  .article {
    width: 100%;
  }

  .aside {
    margin-top: 60px;
    width: 100%;
  }

  .breadcrumb {
    display: flex;
    flex-wrap: wrap;
    padding-top: 20px;
    margin-bottom: -20px;
  }

  .breadcrumbList {
    color: #616269;
    font-size: 14px;

    a {
      color: #331cbf;
    }

    &::after {
      content: '>';
      margin: 0 10px;
    }

    &:last-child&::after {
      content: '';
      margin: 0;
    }
  }

  .main {
    display: flex;
    flex-direction: column-reverse;
    margin-top: 30px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    height: 270px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .ogimageWrap {
    position: relative;
    overflow: hidden;
    width: calc(100% + 32px);
    margin: 0 -16px;
  }

  .ogimage {
    display: block;
    width: 100%;
    transition: transform 0.5s ease;
  }

  .container {
    position: relative;
    flex: 1;
    background-color: #fff;
    -webkit-font-smoothing: antialiased;
  }

  .title {
    font-family: 'Quicksand', 'Source Sans Pro', -apple-system,
      BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
      sans-serif;
    display: block;
    font-weight: bold;
    font-size: 24px;
    color: #2b2c30;
  }
}
</style>


================================================
FILE: pages/index.vue
================================================
<template>
  <div class="wrapper">
    <Header />
    <div class="divider">
      <div class="container">
        <Breadcrumb :category="selectedCategory" :tag="selectedTag" />
        <div v-show="contents.length === 0" class="loader">
          記事がありません
        </div>
        <ul>
          <li v-for="content in contents" :key="content.id" class="list">
            <nuxt-link :to="`/${content.id}/`" class="link">
              <picture v-if="content.ogimage">
                <source
                  type="image/webp"
                  :data-srcset="content.ogimage.url + '?w=670&fm=webp'"
                />
                <img
                  :data-src="content.ogimage.url + '?w=670'"
                  :width="content.ogimage.width"
                  :height="content.ogimage.height"
                  class="ogimage lazyload"
                  alt
                />
              </picture>
              <picture v-else>
                <source
                  type="image/webp"
                  :data-srcset="content.defaultOgimage"
                />
                <img
                  :data-src="content.defaultOgimage"
                  class="ogimage lazyload"
                  alt
                />
              </picture>
              <dl class="content">
                <dt class="title">{{ content.title }}</dt>
                <dd>
                  <Meta
                    :created-at="content.publishedAt || content.createdAt"
                    :author="content.writer"
                    :category="content.category"
                    :tags="content.tag"
                  />
                </dd>
              </dl>
            </nuxt-link>
          </li>
        </ul>
        <Pagination
          v-if="contents.length > 0"
          :pager="pager"
          :current="Number(page)"
          :category="selectedCategory"
          :tag="selectedTag"
        />
      </div>
      <aside class="aside">
        <Banners id="list" :banners="banners" />
        <Search />
        <Categories :categories="categories" />
        <Tags :tags="tags" />
        <PopularArticles :contents="popularArticles" />
      </aside>
    </div>
    <Footer />
  </div>
</template>

<script>
import getDefaultOgimage from '../utils/getDefaultOgimage';

export default {
  async asyncData({ params, payload, $microcms }) {
    const page = params.id || '1';
    const categoryId = params.categoryId;
    const tagId = params.tagId;
    const articleFilter =
      categoryId !== undefined
        ? `category[equals]${categoryId}`
        : tagId !== undefined
        ? `tag[contains]${tagId}`
        : undefined;
    const limit = 10;
    const popularArticles =
      payload !== undefined && payload.popularArticles !== undefined
        ? payload.popularArticles
        : (
            await $microcms.get({
              endpoint: 'popular-articles',
            })
          ).articles;
    const banners =
      payload !== undefined
        ? payload.banners
        : await $microcms.get({
            endpoint: 'banners',
          });
    const data = await $microcms.get({
      endpoint: 'blog',
      queries: {
        limit,
        offset: (page - 1) * limit,
        filters: articleFilter,
      },
    });
    const categories = await $microcms.get({
      endpoint: 'categories',
      queries: {
        limit: 100,
      },
    });
    const tags = await $microcms.get({
      endpoint: 'tags',
      queries: {
        limit: 100,
      },
    });
    const selectedCategory =
      categoryId !== undefined
        ? categories.contents.find((content) => content.id === categoryId)
        : undefined;
    const selectedTag =
      tagId !== undefined
        ? tags.contents.find((content) => content.id === tagId)
        : undefined;

    // デフォルト画像を設定
    data.contents.forEach((content) => {
      content.defaultOgimage = getDefaultOgimage(content);
    });

    return {
      ...data,
      categories: categories.contents,
      tags: tags.contents,
      selectedCategory,
      selectedTag,
      popularArticles,
      banners,
      page,
      pager: [...Array(Math.ceil(data.totalCount / limit)).keys()],
    };
  },
  data() {
    return {
      contents: this.contents || [],
      totalCount: this.totalCount || 0,
      pager: this.pager || [],
      loading: true,
    };
  },
  head() {
    return {
      titleTemplate: null,
      title: 'microCMSブログ',
    };
  },
};
</script>

<style scoped>
@media (min-width: 1160px) {
  .loader {
    color: #ccc;
    font-size: 20px;
    text-align: center;
    padding: 150px;
  }

  .pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    padding: 40px 0 0;
  }

  .page {
    width: 40px;
    height: 40px;
    background-color: #e5eff9;
    border-radius: 5px;
    margin: 10px;

    &.active {
      background-color: #3067af;

      a {
        color: #fff;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      color: #3067af;
    }
  }

  .divider {
    display: flex;
    justify-content: space-between;
    width: 1160px;
    margin: 20px auto 0;
  }

  .container {
    width: 820px;
  }

  .aside {
    width: 300px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .pageTitle {
    font-size: 24px;
    font-weight: bold;
  }

  .list {
    padding: 20px 0;
  }

  .link {
    display: flex;
    justify-content: space-between;
  }

  .ogimage {
    width: 335px;
    height: 176px;
    border-radius: 5px;
  }

  .content {
    flex: 1;
    margin-left: 40px;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
  }
}
@media (min-width: 820px) and (max-width: 1160px) {
  .loader {
    color: #ccc;
    font-size: 16px;
    padding-top: 20px;
  }

  .pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    padding: 40px 0 0;
  }

  .page {
    width: 40px;
    height: 40px;
    background-color: #e5eff9;
    border-radius: 5px;
    margin: 10px;

    &.active {
      background-color: #3067af;

      a {
        color: #fff;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      color: #3067af;
    }
  }

  .divider {
    margin: 20px auto 0;
    width: 740px;
  }

  .article {
    width: 740px;
  }

  .aside {
    margin-top: 60px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .pageTitle {
    font-size: 24px;
    font-weight: bold;
  }

  .list {
    padding: 20px 0;
  }

  .link {
    display: flex;
    justify-content: space-between;
  }

  .ogimage {
    width: 335px;
    height: auto;
    border-radius: 5px;
  }

  .content {
    flex: 1;
    margin-left: 40px;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
  }
}
@media (max-width: 820px) {
  .loader {
    color: #ccc;
    font-size: 16px;
    padding-top: 20px;
  }

  .pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    padding: 40px 0 0;
  }

  .page {
    width: 32px;
    height: 32px;
    background-color: #e5eff9;
    border-radius: 5px;
    margin: 6px;

    &.active {
      background-color: #3067af;

      a {
        color: #fff;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      color: #3067af;
    }
  }

  .divider {
    margin: 20px 0 0;
    padding: 0 20px;
  }

  .article {
    width: 100%;
  }

  .aside {
    margin-top: 60px;
    width: 100%;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .pageTitle {
    font-size: 24px;
    font-weight: bold;
  }

  .list {
    padding: 32px 0 0;
    border-bottom: 1px solid #eee;

    &:first-child {
      padding-top: 16px;
    }
  }

  .ogimage {
    width: 100%;
    height: auto;
    border-radius: 5px;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
    margin-top: 10px;
  }
}
</style>


================================================
FILE: pages/search/index.vue
================================================
<template>
  <div class="wrapper">
    <Header />
    <div class="divider">
      <div class="container">
        <input
          v-model="q"
          class="search"
          type="text"
          @keyup.enter="(e) => search(e.target.value)"
          @keypress="setSearchable"
        />
        <div v-if="loading === true" class="loader">
          <img
            class="loadingIcon"
            src="/images/icon_loading.svg"
            alt="検索中..."
          />
        </div>
        <div v-else>
          <div
            v-show="loading === false && contents.length === 0"
            class="loader"
          >
            記事がありません
          </div>
          <ul>
            <li v-for="content in contents" :key="content.id" class="list">
              <nuxt-link :to="`/${content.id}`" class="link">
                <picture v-if="content.ogimage">
                  <source
                    type="image/webp"
                    :srcset="content.ogimage.url + '?w=670&fm=webp'"
                  />
                  <img
                    :src="content.ogimage.url + '?w=670'"
                    class="ogimage"
                    alt
                  />
                </picture>
                <picture v-else>
                  <source type="image/webp" :srcset="content.defaultOgimage" />
                  <img :src="content.defaultOgimage" class="ogimage" alt />
                </picture>
                <dl class="content">
                  <dt class="title">{{ content.title }}</dt>
                  <dd>
                    <Meta
                      :created-at="content.publishedAt || content.createdAt"
                      :author="content.writer"
                      :category="content.category"
                      :tags="content.tag"
                    />
                  </dd>
                </dl>
              </nuxt-link>
            </li>
          </ul>
          <ul v-show="contents.length > 0" class="pager">
            <li
              v-for="p in pager"
              :key="p"
              class="page"
              :class="{ active: page === `${p + 1}` }"
            >
              <nuxt-link
                :to="`/${
                  selectedCategory !== undefined
                    ? `category/${selectedCategory.id}/`
                    : ''
                }page/${p + 1}`"
              >
                {{ p + 1 }}
              </nuxt-link>
            </li>
          </ul>
        </div>
      </div>
      <aside class="aside">
        <Banners id="search" :banners="banners" />
        <Categories :categories="categories" />
        <Tags :tags="tags" />
        <PopularArticles :contents="popularArticles" />
      </aside>
    </div>
    <Footer />
  </div>
</template>

<script>
import axios from 'axios';
import getDefaultOgimage from '../../utils/getDefaultOgimage';

export default {
  async asyncData({ payload, $microcms }) {
    const popularArticles =
      payload !== undefined && payload.popularArticles !== undefined
        ? payload.popularArticles
        : (
            await $microcms.get({
              endpoint: 'popular-articles',
            })
          ).articles;
    const banners =
      payload !== undefined
        ? payload.banners
        : await $microcms.get({
            endpoint: 'banners',
          });
    const categories = await $microcms.get({
      endpoint: 'categories',
      queries: {
        limit: 100,
      },
    });
    const tags = await $microcms.get({
      endpoint: 'tags',
      queries: {
        limit: 100,
      },
    });
    return {
      popularArticles,
      banners,
      categories: categories.contents,
      tags: tags.contents,
    };
  },
  data() {
    return {
      searchable: true,
      contents: this.contents || [],
      totalCount: this.totalCount || 0,
      categories: this.categories || [],
      pager: this.pager || [],
      loading: true,
      q: this.$route.query.q,
    };
  },
  created() {
    const query = this.$route.query;
    this.search(query.q);
  },
  methods: {
    setSearchable() {
      this.searchable = true;
    },
    async search(q = '') {
      if (!q.trim() || !this.searchable) {
        return;
      }
      this.loadingStart();
      const { data, error } = await axios
        .get(`/.netlify/functions/search?q=${q}`)
        .catch((error) => ({ error }));
      this.loadingFinish();
      if (error) {
        return;
      }
      this.contents = data.contents;
      this.contents.forEach((content) => {
        content.defaultOgimage = getDefaultOgimage(content);
      });
      this.totalCount = data.totalCount;
      this.searchable = false;
    },
    loadingStart() {
      this.loading = true;
    },
    loadingFinish() {
      this.loading = false;
    },
  },
  head() {
    return {
      titleTemplate: null,
      title: 'microCMSブログ',
    };
  },
};
</script>

<style scoped>
.search {
  border: 1px solid var(--color-border);
  width: 100%;
  box-sizing: border-box;
  border-radius: 5px;
  height: 40px;
  font-size: 16px;
  background: url('/images/icon_search.svg') no-repeat 10px center,
    var(--color-bg-purple-light);
  padding-left: 40px;
  margin-bottom: 20px;
  box-shadow: none;
  -webkit-appearance: none;
  transition: box-shadow 0.2s ease;

  &:hover {
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1) inset;
  }

  &:focus {
    outline: none;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1) inset;
  }
}

.loadingIcon {
  width: 38px;
  height: 38px;
}

@media (min-width: 1160px) {
  .loader {
    color: #ccc;
    font-size: 20px;
    text-align: center;
    padding: 150px;
  }

  .pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    padding: 40px 0 0;
  }

  .page {
    width: 40px;
    height: 40px;
    background-color: #e5eff9;
    border-radius: 5px;
    margin: 10px;

    &.active {
      background-color: #3067af;

      a {
        color: #fff;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      color: #3067af;
    }
  }

  .divider {
    display: flex;
    justify-content: space-between;
    width: 1160px;
    margin: 20px auto 0;
  }

  .container {
    width: 820px;
  }

  .aside {
    width: 300px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .pageTitle {
    font-size: 24px;
    font-weight: bold;
  }

  .list {
    padding: 20px 0;
  }

  .link {
    display: flex;
    justify-content: space-between;
  }

  .ogimage {
    width: 335px;
    height: 176px;
    border-radius: 5px;
  }

  .content {
    flex: 1;
    margin-left: 40px;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
  }
}
@media (min-width: 820px) and (max-width: 1160px) {
  .loader {
    color: #ccc;
    text-align: center;
    font-size: 16px;
    padding-top: 20px;
  }

  .pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    padding: 40px 0 0;
  }

  .page {
    width: 40px;
    height: 40px;
    background-color: #e5eff9;
    border-radius: 5px;
    margin: 10px;

    &.active {
      background-color: #3067af;

      a {
        color: #fff;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      color: #3067af;
    }
  }

  .divider {
    margin: 20px auto 0;
    width: 740px;
  }

  .article {
    width: 740px;
  }

  .aside {
    margin-top: 60px;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    width: 300px;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .pageTitle {
    font-size: 24px;
    font-weight: bold;
  }

  .list {
    padding: 20px 0;
  }

  .link {
    display: flex;
    justify-content: space-between;
  }

  .ogimage {
    width: 335px;
    height: 176px;
    border-radius: 5px;
  }

  .content {
    flex: 1;
    margin-left: 40px;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
  }
}
@media (max-width: 820px) {
  .loader {
    color: #ccc;
    text-align: center;
    font-size: 16px;
    padding-top: 20px;
  }

  .pager {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    padding: 40px 0 0;
  }

  .page {
    width: 32px;
    height: 32px;
    background-color: #e5eff9;
    border-radius: 5px;
    margin: 6px;

    &.active {
      background-color: #3067af;

      a {
        color: #fff;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      color: #3067af;
    }
  }

  .divider {
    margin: 20px 0 0;
    padding: 0 20px;
  }

  .article {
    width: 100%;
  }

  .aside {
    margin-top: 60px;
    width: 100%;
  }

  .banner {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    height: 250px;
    background-color: #2b2c30;
    color: #fff;
    border-radius: 5px;

    img {
      width: 160px;
      margin-top: 10px;
    }

    p {
      margin-top: 30px;
      color: #999;
      padding-top: 10px;
      font-size: 14px;
      width: 260px;
      text-align: center;
      border-top: 1px solid #666;
    }

    span {
      display: block;
      border: 1px solid #fff;
      width: 120px;
      margin: 0 auto;
      text-align: center;
      margin-top: 10px;
      padding: 4px 0;
      font-size: 14px;
    }
  }

  .pageTitle {
    font-size: 24px;
    font-weight: bold;
  }

  .list {
    padding: 32px 0 0;
    border-bottom: 1px solid #eee;

    &:first-child {
      padding-top: 16px;
    }
  }

  .ogimage {
    width: 100%;
    border-radius: 5px;
  }

  .title {
    font-size: 20px;
    font-weight: bold;
    margin-top: 10px;
  }
}
</style>


================================================
FILE: plugins/README.md
================================================
# PLUGINS

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).


================================================
FILE: plugins/vue-scrollto.js
================================================
import Vue from 'vue';
import VueScrollTo from 'vue-scrollto';

Vue.use(VueScrollTo);

export default function vueScrollTo(context, inject) {
  inject('scrollTo', VueScrollTo.scrollTo);
}


================================================
FILE: static/README.md
================================================
# STATIC

**This directory is not required, you can delete it if you don't want to use it.**

This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.

Example: `/static/robots.txt` is mapped as `/robots.txt`.

More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).


================================================
FILE: static/robots.txt
================================================
User-agent: *
Allow: /
Disallow: /draft/
Sitemap: https://blog.microcms.io/sitemap.xml
Crawl-delay: 1

================================================
FILE: utils/getDefaultOgimage.js
================================================
export default function getDefaultOgimage(content) {
  const encodedTitle = encodeURI(content.title);
  const length = content.title.length;
  const textSize = length > 36 ? 56 : length > 22 ? 64 : length > 14 ? 74 : 84; // 正確な文字数ではないが大体の指標としては十分と判断する
  return `https://images.blog.microcms.io/assets/f5d83e38f9374219900ef1b0cc4d85cd/92c09085ec6243cca78046fa644dd8cd/banner-bg.png?blend-mode=normal&blend-x=78&blend-y=200&blend64=${Buffer.from(
    `https://assets.imgix.net/~text?txtsize=${textSize}&w=672&txtfont=Noto%20Sans%20JP%20Black&txt-color=212149&txt=${encodedTitle}`,
    'ascii'
  ).toString('base64')}`;
}


================================================
FILE: utils/microcms.js
================================================
const { createClient } = require('microcms-js-sdk');
require('dotenv').config();
const { API_KEY, SERVICE_ID } = process.env;
export const client = createClient({
  serviceDomain: SERVICE_ID,
  apiKey: API_KEY,
});
Download .txt
gitextract__39a8pyr/

├── .eslintrc.js
├── .gitignore
├── .node-version
├── .prettierrc
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── LICENSE
├── README.md
├── assets/
│   ├── README.md
│   └── styles/
│       ├── colors.css
│       └── reset.css
├── components/
│   ├── Alert.vue
│   ├── Banners.vue
│   ├── Breadcrumb.vue
│   ├── Card.vue
│   ├── Categories.vue
│   ├── ConversionPoint.vue
│   ├── Cta.vue
│   ├── Footer.vue
│   ├── Header.vue
│   ├── Latest.vue
│   ├── Logo.vue
│   ├── Meta.vue
│   ├── NextBlogNavigation.vue
│   ├── Pagination.vue
│   ├── Partner.vue
│   ├── PopularArticles.vue
│   ├── Post.vue
│   ├── README.md
│   ├── RelatedBlogs.vue
│   ├── Search.vue
│   ├── Share.vue
│   ├── ShareButtons.vue
│   ├── Tags.vue
│   ├── Toc.vue
│   └── Writer.vue
├── functions/
│   ├── draft.js
│   └── search.js
├── layouts/
│   ├── README.md
│   └── default.vue
├── netlify.toml
├── nuxt.config.js
├── package.json
├── pages/
│   ├── 404.vue
│   ├── README.md
│   ├── _slug/
│   │   └── index.vue
│   ├── author/
│   │   └── _authorId.vue
│   ├── draft/
│   │   └── index.vue
│   ├── index.vue
│   └── search/
│       └── index.vue
├── plugins/
│   ├── README.md
│   └── vue-scrollto.js
├── static/
│   ├── README.md
│   └── robots.txt
└── utils/
    ├── getDefaultOgimage.js
    └── microcms.js
Download .txt
SYMBOL INDEX (9 symbols across 3 files)

FILE: nuxt.config.js
  method extend (line 165) | extend(config, ctx) {
  method extendRoutes (line 178) | extendRoutes(routes, resolve) {
  method routes (line 208) | async routes() {
  method create (line 375) | async create(feed) {
  method create (line 409) | async create(feed) {
  method create (line 440) | async create(feed) {
  method create (line 474) | async create(feed) {

FILE: plugins/vue-scrollto.js
  function vueScrollTo (line 6) | function vueScrollTo(context, inject) {

FILE: utils/getDefaultOgimage.js
  function getDefaultOgimage (line 1) | function getDefaultOgimage(content) {
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (210K chars).
[
  {
    "path": ".eslintrc.js",
    "chars": 373,
    "preview": "module.exports = {\n  root: true,\n  env: {\n    browser: true,\n    node: true,\n  },\n  parserOptions: {\n    parser: 'babel-"
  },
  {
    "path": ".gitignore",
    "chars": 1524,
    "preview": "# Created by .ignore support plugin (hsz.mobi)\n### Node template\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-e"
  },
  {
    "path": ".node-version",
    "chars": 7,
    "preview": "16.20.0"
  },
  {
    "path": ".prettierrc",
    "chars": 42,
    "preview": "{\n  \"semi\": true,\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 95,
    "preview": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\",\n    \"simonsiefke.prettier-vscode\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 109,
    "preview": "{\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"editor.formatOnSave\": true\n}"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 3929,
    "preview": "# microcms-blog\nサイト: https://blog.microcms.io\n\n## 機能\n- 記事一覧\n- カテゴリー別記事一覧\n- タグ別記事一覧\n- 人気の記事一覧\n- 最新の記事一覧\n- 著者別の記事一覧\n- 検索\n-"
  },
  {
    "path": "assets/README.md",
    "chars": 296,
    "preview": "# ASSETS\n\n**This directory is not required, you can delete it if you don't want to use it.**\n\nThis directory contains yo"
  },
  {
    "path": "assets/styles/colors.css",
    "chars": 1226,
    "preview": ":root {\n  --color-text-main: #2b2c30;\n  --color-text-sub: #616269;\n  --color-text-off: #999;\n  --color-text-disabled: #c"
  },
  {
    "path": "assets/styles/reset.css",
    "chars": 2035,
    "preview": "/* http://meyerweb.com/eric/tools/css/reset/ \nv2.0 | 20110126\nLicense: none (public domain)\n*/\n\nhtml, body, div, span, a"
  },
  {
    "path": "components/Alert.vue",
    "chars": 850,
    "preview": "<template>\n  <div v-if=\"isOldPost && isTechnicalPost\" class=\"alert\">\n    <p>\n      この記事は公開後、1年以上経過しています。情報が古い可能性がありますので、"
  },
  {
    "path": "components/Banners.vue",
    "chars": 2863,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <template v-for=\"(item, idx) in normalizedBanners\">\n      <!-- セクションタイトル -->\n    "
  },
  {
    "path": "components/Breadcrumb.vue",
    "chars": 1147,
    "preview": "<template>\n  <ul class=\"breadcrumb\">\n    <li class=\"breadcrumbList\">\n      <nuxt-link to=\"/\">記事一覧</nuxt-link>\n    </li>\n"
  },
  {
    "path": "components/Card.vue",
    "chars": 2657,
    "preview": "<template class=\"list\">\n  <nuxt-link :to=\"`/${content.id}/`\" class=\"link\">\n    <picture v-if=\"content.ogimage\">\n      <s"
  },
  {
    "path": "components/Categories.vue",
    "chars": 1701,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <h1 class=\"pageTitle\">カテゴリー</h1>\n    <ul>\n      <li v-for=\"category in categories"
  },
  {
    "path": "components/ConversionPoint.vue",
    "chars": 8415,
    "preview": "<template>\n  <div :class=\"{ [`cvPoint--${theme}`]: theme !== '' }\" class=\"wrapper cvPoint\">\n    <div v-if=\"theme === 'th"
  },
  {
    "path": "components/Cta.vue",
    "chars": 5272,
    "preview": "<template>\n  <div v-if=\"contents.length > 0\" class=\"wrapper\">\n    <div v-for=\"(item, index) in getContents\" :key=\"index\""
  },
  {
    "path": "components/Footer.vue",
    "chars": 13639,
    "preview": "<template>\n  <footer class=\"footer\">\n    <h2 class=\"visuallyHidden\">サイトマップ</h2>\n    <div class=\"container\">\n      <ul cl"
  },
  {
    "path": "components/Header.vue",
    "chars": 12946,
    "preview": "<template>\n  <div>\n    <header class=\"header\">\n      <h1 class=\"logo\">\n        <a href=\"https://microcms.io\">\n          "
  },
  {
    "path": "components/Latest.vue",
    "chars": 1625,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <h1 class=\"pageTitle\">最新の記事</h1>\n    <ul>\n      <li v-for=\"content in contents\" :"
  },
  {
    "path": "components/Logo.vue",
    "chars": 876,
    "preview": "<template>\n  <svg\n    class=\"NuxtLogo\"\n    width=\"245\"\n    height=\"180\"\n    viewBox=\"0 0 452 342\"\n    xmlns=\"http://www."
  },
  {
    "path": "components/Meta.vue",
    "chars": 5157,
    "preview": "<template>\n  <div>\n    <div class=\"upper\">\n      <template v-if=\"category && isSinglePage\">\n        <nuxt-link class=\"ca"
  },
  {
    "path": "components/NextBlogNavigation.vue",
    "chars": 1779,
    "preview": "<template>\n  <div v-if=\"previous || next\" class=\"navigation\">\n    <ul>\n      <li class=\"previous\">\n        <nuxt-link v-"
  },
  {
    "path": "components/Pagination.vue",
    "chars": 2897,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <ul class=\"pager\">\n      <li v-if=\"current > 1\" class=\"page arrow\">\n        <nuxt"
  },
  {
    "path": "components/Partner.vue",
    "chars": 2907,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <div v-if=\"partner\" class=\"container\">\n      <picture v-if=\"partner.logo\">\n      "
  },
  {
    "path": "components/PopularArticles.vue",
    "chars": 2702,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <h1 class=\"pageTitle\">人気の記事</h1>\n    <ul>\n      <li v-for=\"content in contents\" :"
  },
  {
    "path": "components/Post.vue",
    "chars": 4809,
    "preview": "<template>\n  <div class=\"post\" v-html=\"body\"></div>\n</template>\n\n<script>\nexport default {\n  props: {\n    body: {\n      "
  },
  {
    "path": "components/README.md",
    "chars": 205,
    "preview": "# COMPONENTS\n\n**This directory is not required, you can delete it if you don't want to use it.**\n\nThe components directo"
  },
  {
    "path": "components/RelatedBlogs.vue",
    "chars": 2955,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <h2 class=\"pageTitle\">関連記事</h2>\n    <ul class=\"lists\">\n      <li v-for=\"blog in b"
  },
  {
    "path": "components/Search.vue",
    "chars": 1205,
    "preview": "<template>\n  <label class=\"label\">\n    サイト内検索\n    <input\n      class=\"input\"\n      type=\"text\"\n      @keypress=\"setSearc"
  },
  {
    "path": "components/Share.vue",
    "chars": 2398,
    "preview": "<template>\n  <div class=\"share\">\n    <ul class=\"shareLists\">\n      <li class=\"shareList\">\n        <a :href=\"twitterLink\""
  },
  {
    "path": "components/ShareButtons.vue",
    "chars": 2915,
    "preview": "<template>\n  <div class=\"share\">\n    <h3 class=\"title\">記事をシェアする</h3>\n    <ul class=\"shareLists\">\n      <li class=\"shareL"
  },
  {
    "path": "components/Tags.vue",
    "chars": 1981,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <h1 class=\"pageTitle\">タグ</h1>\n    <ul class=\"list\">\n      <li v-for=\"tag in tags\""
  },
  {
    "path": "components/Toc.vue",
    "chars": 1038,
    "preview": "<template>\n  <div v-if=\"visible\" class=\"wrapper\">\n    <h4 class=\"title\">目次</h4>\n    <ul class=\"lists\">\n      <li v-for=\""
  },
  {
    "path": "components/Writer.vue",
    "chars": 2317,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <h2 class=\"title\">ABOUT ME</h2>\n    <div class=\"container\">\n      <picture v-if=\""
  },
  {
    "path": "functions/draft.js",
    "chars": 673,
    "preview": "const { client } = require('../utils/microcms');\n\n// eslint-disable-next-line require-await\nexports.handler = async (eve"
  },
  {
    "path": "functions/search.js",
    "chars": 599,
    "preview": "const { client } = require('../utils/microcms');\n\n// eslint-disable-next-line require-await\nexports.handler = async (eve"
  },
  {
    "path": "layouts/README.md",
    "chars": 261,
    "preview": "# LAYOUTS\n\n**This directory is not required, you can delete it if you don't want to use it.**\n\nThis directory contains y"
  },
  {
    "path": "layouts/default.vue",
    "chars": 108,
    "preview": "<template>\n  <div>\n    <nuxt />\n  </div>\n</template>\n\n<style>\n@import '@/assets/styles/reset.css';\n</style>\n"
  },
  {
    "path": "netlify.toml",
    "chars": 6014,
    "preview": "[build]\n  functions = \"dist/api\"\n\n[[redirects]]\n  from = \"/usecase-cainz/\"\n  to = \"https://microcms.io/interviews/cainz\""
  },
  {
    "path": "nuxt.config.js",
    "chars": 13526,
    "preview": "import { client } from './utils/microcms';\nconst { API_KEY, SERVICE_ID, GTM_ID, FB_PIXEL_ID } = process.env;\n\nexport def"
  },
  {
    "path": "package.json",
    "chars": 1446,
    "preview": "{\n  \"name\": \"microcms-blog\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"nuxt\",\n    \"generate\": "
  },
  {
    "path": "pages/404.vue",
    "chars": 720,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <Header />\n    <div class=\"container\">\n      <dl>\n        <dt class=\"status\">404<"
  },
  {
    "path": "pages/README.md",
    "chars": 286,
    "preview": "# PAGES\n\nThis directory contains your Application Views and Routes.\nThe framework reads all the `*.vue` files inside thi"
  },
  {
    "path": "pages/_slug/index.vue",
    "chars": 14417,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <Header />\n    <div class=\"divider\">\n      <article class=\"article\">\n        <div"
  },
  {
    "path": "pages/author/_authorId.vue",
    "chars": 7204,
    "preview": "<template>\n  <div id=\"author\" class=\"wrapper\">\n    <Header />\n\n    <div class=\"profile\">\n      <div class=\"inner\">\n     "
  },
  {
    "path": "pages/draft/index.vue",
    "chars": 14463,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <Header />\n    <div class=\"divider\">\n      <p v-if=\"!data.id\" class=\"loading\">Now"
  },
  {
    "path": "pages/index.vue",
    "chars": 9895,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <Header />\n    <div class=\"divider\">\n      <div class=\"container\">\n        <Bread"
  },
  {
    "path": "pages/search/index.vue",
    "chars": 10951,
    "preview": "<template>\n  <div class=\"wrapper\">\n    <Header />\n    <div class=\"divider\">\n      <div class=\"container\">\n        <input"
  },
  {
    "path": "plugins/README.md",
    "chars": 314,
    "preview": "# PLUGINS\n\n**This directory is not required, you can delete it if you don't want to use it.**\n\nThis directory contains J"
  },
  {
    "path": "plugins/vue-scrollto.js",
    "chars": 188,
    "preview": "import Vue from 'vue';\nimport VueScrollTo from 'vue-scrollto';\n\nVue.use(VueScrollTo);\n\nexport default function vueScroll"
  },
  {
    "path": "static/README.md",
    "chars": 435,
    "preview": "# STATIC\n\n**This directory is not required, you can delete it if you don't want to use it.**\n\nThis directory contains yo"
  },
  {
    "path": "static/robots.txt",
    "chars": 101,
    "preview": "User-agent: *\nAllow: /\nDisallow: /draft/\nSitemap: https://blog.microcms.io/sitemap.xml\nCrawl-delay: 1"
  },
  {
    "path": "utils/getDefaultOgimage.js",
    "chars": 619,
    "preview": "export default function getDefaultOgimage(content) {\n  const encodedTitle = encodeURI(content.title);\n  const length = c"
  },
  {
    "path": "utils/microcms.js",
    "chars": 215,
    "preview": "const { createClient } = require('microcms-js-sdk');\nrequire('dotenv').config();\nconst { API_KEY, SERVICE_ID } = process"
  }
]

About this extraction

This page contains the full source code of the wantainc/microcms-blog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (186.2 KB), approximately 54.8k tokens, and a symbol index with 9 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!