Next.jsのPreview modeでページのプレビューを表示する
Next.js v9.3でpreview modeが追加されました
CMSっぽいの作ろうと思ってるので、ちょっと試してみました
めちゃシンプルで簡単にPreviewできたのでメモ
ドキュメントは↓ nextjs.org
サンプルコードは↓ github.com
Preview modeのAPIを用意する
ヘッドレスCMDなどからプレビューページを表示するためのエンドポイントを作成します
ぼくのサンプルコードではこんな感じ
// /pages/api/preview.ts export default (req, res) => { const id = req.query.id; res.setPreviewData({}); res.writeHead(307, { Location: `/posts/${id}` }); res.end(); };
クエリパラメータのid
で対象のURLへリダイレクトしているだけです
setPreviewData
には任意のデータを渡せますが、cookieで保持しているのでサイズには上限があります
previewDataのリミットは2KBだそうです
Pagesでpreview mdoeの振る舞いを記述する
Preview modeだとgetStaticPropsのpropsにpreview: true
とpreviewDataL {}
が渡ってきます
ぼくのサンプルコードではpreview
をcomponentに渡してラベルを表示するようにしました
// /pages/posts/[id].tsx import { useRouter } from 'next/router'; export const getStaticPaths = async () => { return { paths: [{ params: { id: '1' } }], fallback: true, }; }; export const getStaticProps = async ({ params, preview }) => { const isPreview = preview || false; return { props: { id: params.id, previewMode: isPreview } }; }; const Post = ({ id, previewMode }) => { const router = useRouter(); if (router.isFallback) { return <div>Loading...</div>; } return ( <> {previewMode ? 'Preview mode' : ''} <h1>{`Page's ID is ${id}`}</h1> </> ); }; export default Post;
これだけです!
http://localhost:3000/api/preview?id=1
にアクセスすると/pages/1
にリダイレクトされて、Preview modeのラベルが表示されるはずです
Previewモードを解除する
Preview modeを解除するには、cookieをクリアしてあげればOKです
解除用のエンドポイントを作成します
// /pages/api/preview-disable.ts export default (req, res) => { const location = req.headers.referer || '/'; res.clearPreviewData(); res.writeHead(307, { Location: location }); res.end(); };
pageにも解除用のリンクを追加します
// /pages/posts/[id].tsx import { useRouter } from 'next/router'; export const getStaticPaths = async () => { return { paths: [{ params: { id: '1' } }], fallback: true, }; }; export const getStaticProps = async ({ params, preview }) => { const isPreview = preview || false; return { props: { id: params.id, previewMode: isPreview } }; }; const Post = ({ id, previewMode }) => { const router = useRouter(); if (router.isFallback) { return <div>Loading...</div>; } return ( <> {previewMode ? 'Preview mode' : ''} <h1>{`Page's ID is ${id}`}</h1> {previewMode ? ( <a href="/api/preview-disable">Disable preview mode</a> ) : null} // リンクを追加 </> ); }; export default Post;
このリンクをクリックすると、posts/1
にリダイレクトしてPreview modeのラベルやリンクが表示されないはずです
まとめ
すごくシンプルで簡単にPreviewを実装できました
実際の運用では、previewのデータを取得する実装が必要になりますね
公開されているデータを取得するclinetとPreviewのデータを取得するclientを作って、getStaticProps
で切り替えるとかね
Enjoy Next.js ヘ(^o^)ノ
Next.jsでTailwind CSSとEmotionを使う
Next.jsでTailwind CSSとEmotionを使える環境を作ってみます
$ npm init next-app --example with-tailwindcss-emotion [アプリケーション名]
create-next-appを使って、exampleをダウンロードするだけでOKです
Exampleのリポジトリ
これだけだとあっけないので、Tips的なものを1つ
[ご注意] ここからは↑で作成したexampleを前提に書いてます。他の環境では動作しないかもです
こんなコンポーネントがあったとします
/** @jsx jsx */ import { jsx } from '@emotion/react' import tw from '@tailwindcssinjs/macro' const styles = { button: tw` relative ` } const Button = () => <button css={styles.button}>button</button> export default Button
JSX Pragmaなるもの(/** @jsx jsx */
)とimport { jsx } from '@emotion/react'
を毎回かかないといけません。。。
しかも、ESLintにjsx
がno-unused-vars
だと怒られたりもします。。。
そこで、@emotion/babel-preset-css-prop
ですよ
@emotion/babel-preset-css-prop
は、Emotion用パーサーのbabelプラグインです
導入手順
- Installation
- @emotion/core
@emotion/babel-preset-css-prop
には@emotion/core
が必要なのでインストール
- @emotion/core
$ npm install @emotion/core or $ yarn add @emotion/core
- @emotion/babel-preset-css-prop
$ npm install @emotion/babel-preset-css-prop or $ yarn add @emotion/babel-preset-css-prop
- Modify babel config
{ "presets": [ "next/babel", "@emotion/babel-preset-css-prop" # この行を追加 ], "plugins": [ "macros", "@emotion/babel-plugin" ] }
これで ↓ の2行が不要になります
/** @jsx jsx */ import { jsx } from '@emotion/react'
やったね!
あ、TypeScriptだとcssってpropsの型しらねーって怒られるので、型定義も追加する必要があります
# emotion-env.d.ts (ファイル名はなんでもOK)
/// <reference types="@emotion/core"/>
Happy Tailwind CSS and Emotion ヘ(^o^)ノ
Railsのmodelでprivateメソッドのロジックをテストしない
みなさんはRailsのモデルでprivateメソッドをテストしますか?
僕はしません。
理由はpublicメソッドではないからです。
Privateメソッドをテストするって理由の一つが、「privateメソッドのロジックをテストしたい」だと思います。
ふーん、でもそのロジックはそのモデルにあるべきなんでしょうか?そして、そのモデルでテストするべきなのでしょうか?
他のモデルで同じロジックを使いたいときはどうするのでしょうか?
Railsにはconcernという機能がありますが、僕は基本使わなくなりました。
なせ?単純にいらないからですw
嘘です、必要です。そもそもconcernの存在意義が、ロジックを共通化するものではないというだけです。
では、どうするのか?
そのロジック用のモデルを作成することで解決できます。
具体的にやってみましょう。
Humanモデルにロジックを書いてみる
class Creature include ActiveModel::Model include AcitveMode::Attributes attribute :name, :string end class Human < Creature private def hello "Hello #{name}!" end def hi "Hi #{name}!" end end
hello
と hi
メソッドをテストしたいよね?
でも、privateメソッド、、
ならば!
Greetモデルを作成してみる
class Human < Creature private def greet @greet ||= Greet.new(creature: self) end end
class Greet include ActiveModel::Model include AcitveMode::Attributes attribute :creature def hello "Hello #{creature&.name}!" end def hi "Hi #{creature&.name}!" end end
どうでしょうか?
これでprivateメソッドのテストは不要になりますね。(Greetモデルのテストで担保するため)
さらに移譲してみましょう。
class Human < Creature delegate :hello, :hi, to: :greet private def greet @greet ||= Greet.new(creature: self) end end
irb>human = Human.new(name: 'murajun1978') irb>human.hello #=> "Hello murajun1978!" irb>human.hi #=> "Hi murajun1978!"
みなさんも、新しいロジックがひょこっと出てきたら、新しいモデルを作成しましょう。
そのモデルをテストすることで品質も担保できますしね。
Apollo serverのSchema directivesを使ってdeprecation warningを表示する
Apollo serverでリファクタリングや仕様変更などで、特定のフィールドをdeprecatedにしたい場合があります
Apollo serverの @deprecated
を使えば簡単にdeprecation warningを表示できます
では、やってみましょう
// src/index.js const { ApolloServer, gql } = require("apollo-server"); const typeDefs = gql` type Author { name: String } type Book { title: String author: Author authorName: String } type Query { books: [Book] } `; const resolvers = {}; const server = new ApolloServer({ typeDefs, mocks: true }); server.listen(4001).then(({ url }) => { console.log(`🚀 Server ready at ${url}`); });
BookのauthorName
をdeprecatedにしたいので、deprecation warningを表示したいと思います
type Book { title: String author: Author authorName: String @deprecated( reason: "\`authorName\` is deprecated. Use \`author\` instead." ) }
これだけです!超簡単!
Playgroundで確認してみましょう!
DOCSもみてみましょう
ちゃんと表示されてますね!
schemaDirectivesは自分で定義することもできるので、特定のフィールドを表示するかを認可で切り替えとかもできますね
Happy GraphQL ヘ(^o^)ノ
RailsでYAMLに定義したカスタム設定を使う
Railsで自分で定義したYAMLファイルをロードしてアプリケーションで使いたい!
ってことでやってみますー
僕がRailsでカスタム設定といえば、そうこの子です!
Latest commit 0ae134b on Aug 7, 2014
なるほど、開発やめちゃったんだねー
そう言えばこんな子もいたよ
Latest commit 9a670d4 22 days ago
うん、大丈夫そう!これを使お、、う?
ちょっと気になったことが、
僕がゴリゴリRailsでアプリ作りまくってたときは、settingslogicはみんな使ってた
なのに、更新されてない???
なんで?
そこでRails本体のconfig周りを調べてみました!
すると、、
config_for
って知らない子ですねー
どうやら、Railsのrootディレクトリ/config配下にあるYAMLファイルをシュッとロードしてくれるメソッドみたいです
# config/my_settings.yml default: &default setting_name: My setting development: <<: *default
irb(main):001:0> Rails.application.config_for(:my_settings) => {:setting_name=>"My setting"}
第2引数の env
のデフォルトは Rails.env
なので省略可能です
環境変数を使ってみましょう!
# config/my_settings.yml default: &default setting_name: <%= ENV.fetch("MY_SETTING_NAME","") %> development: <<: *default
irb(main):001:0> ENV["MY_SETTING_NAME"] = "My setting name" irb(main):002:0> Rails.application.config_for(:my_settings) => {:setting_name=>"My setting name"}
settingslogicやconfigなどのgemがなくても、簡単にYAMLフィアルをロードできました!
Railsガイドにもちゃんと書かれてましたー
Markdown BlogをNext.jsで作る
はてなブログに書いてるけど、興味のある技術を試す場としてブログを自分でbuildしようかと思う
Markdownで記事を書くなら、GatsbyJSとかでさくっとできそうだけど、そこはあえて自分が興味あるもので作ってみる
Next.js + MDX + TypeScript でブログを構築していく
Next.js
Installation
$ npm install next react react-dom $ npm install -D @types/node @types/react typescript
Add Index and Blog page
// src/pages/index.tsx import Link from 'next/link'; const Index = () => ( <div> <Link href="/blog"> <a>Blog</a> </Link> </div> ); export default Index;
// src/pages/blog.tsx const Blog = () => ( <div> <p>Welcome to my blog</p> </div> ); export default Blog;
Add npm script
# package.json { ... "scripts": { "dev": "next", "build": "next build", "start": "next start" } }
Start Next.js
$ npm run dev
これでindexページとblogページが表示できるはず
僕はVSCodeを使ってdebugしたいので ↓ inspect用のスクリプトも用意している
# package.json { ... "scripts": { "dev": "next", "dev:inspect": "NODE_OPTIONS='--inspect=0.0.0.0' next", "build": "next build", "start": "next start" } }
# .vscode/launch.json { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Docker: Next.js", "skipFiles": [ "<node_internals>/**" ], "remoteRoot": "/home/app", "protocol": "inspector", "port": 9229 } ] }
MDX
最初は react-markdown + gray-matter で行こうと思ったけど、
MDX が良さそうだったので採用
MDXはMarkdownをJSXで使えちゃうし、導入も楽ちんだった!
Installation
$ npm install @next/mdx @mdx-js/loader
// next.config.js const withMDX = require("@next/mdx")({ extension: /\.mdx?$/ }); module.exports = withMDX({ pageExtensions: ["ts", "tsx", "mdx"] });
// mdx.d.ts declare module '*.mdx' { // eslint-disable-next-line @typescript-eslint/no-explicit-any let MDXComponent: (props: any) => JSX.Element; export default MDXComponent; }
# tsconfig.json { ... "include": [ "mdx.d.ts", # 追加 "next-env.d.ts", "**/*.ts", "**/*.tsx" ] }
Add Sample post
// src/posts/sample_post.mdx export const meta = { title: 'Sample Post', }; # Sample Post ## Welcome to My Blog
// src/pages/blog.tsx import SamplePost from '../posts/sample_post.mdx'; const Blog = () => ( <div> <SamplePost /> </div> ); export default Blog;
参考
これでmarkdownの記事を表示することができました{^^)
【現場で役立つシステム設計の原則】ドメインオブジェクトの見つけ方
現場で役立つシステム設計の原則をよみ直している
ドメインオブジェクトの見つけ方
かなり適当に言ってしまうと、会話だと思いました! (実際、そう書いてある)
この辺は僕もあやしくて、多分こんな感じーって実装してしまいます
ですが、優秀なエンジニアさんはめっちゃ聞きますw
業務でやってることや困ってることを聞いて、それを解決するアーキテクチャを決めてコードを書くって流れでしょうか
そう、いいプロダクトは会話からできると言っても過言ではないかもですねー
ま、人が使うものを人が作るわけで当たり前と言えば当たり前です
見つけ方ですが、ざっくり全体のフローを聞き取りしてから、手帳なりホワイトボードなりに書くのがよさそうです
プロジェクトチームから常に見えるところに、ドットペーパーに付箋を貼っておくのもいいかも
業務の関心ごとをヒト、モノ、コトに分類して整理する方法が紹介されています
業務を理解するときに使う図法も紹介されています
- コンテキスト図
- 業務フロー図
- パッケージ図
- クラス図
これらの図から、ドメインオブジェクトを見つけクラスにしていきます
そう、ドメインモデルは業務知識の塊なのです
なので、新たな業務が発見されると、必然的にクラスが追加され、そのクラスにはロジックが記述されます
そうして、業務知識を得ながらドメインモデルを育てていきます
もっとも大事だと感じたのは、クラス名やメソッド名ですね (ここも僕はいまいち自信がないw
業務で使っている言葉を、ドメインモデルに起こして、ロジック (処理) がメソッド名とマッチしていれば、業務知識(ドメイン)がコードに落ちている状態だと言えますね
うん、あとから参加したメンバーにもわかりやすく良さそうです
中途半端なドキュメントを保守するより、業務ドメインをコードに落とし込み、テストコードで仕様を確認できるとすばらし