- Install commitizen globally:
npm install commitizen -g
- Install dependencies:
npm i
- Environment variables:
cp .env.local.example .env.local
- Run
npm run gen:type
- Run
npm run i18n
- Run
npm run dev
, then go tohttp://localhost:3000/
npm run build && npm run start
- Environment variables:
cp .env.local.example .env
- Set command alias:
source bin/dc-alias
- Build docker image:
dc build
- Run:
dc up
ordc run --service-ports web npm run dev
- then go to
http://localhost:3000/
NOTE: If new packages are added to package.json,
dc up
will usenpm i
to install those packages. But if you are usingdc run --service-ports web npm run dev
, you need to rundc run web npm i
manually to make sure that new packages are installed.
- Set command alias:
source bin/dc-alias
dc run web npm run build
- Set command alias:
source bin/dc-alias
- Build docker image:
dc build
- aws configure, then input your access key and secret
- Login AWS ECR with
$(aws ecr get-login --no-include-email --region ap-southeast-1)
- Push:
docker push 903380195283.dkr.ecr.ap-southeast-1.amazonaws.com/matters-web:latest
docker tag matters-web:latest 903380195283.dkr.ecr.ap-southeast-1.amazonaws.com/matters-web:latest
- Pull:
docker pull 903380195283.dkr.ecr.ap-southeast-1.amazonaws.com/matters-web:latest
docker tag 903380195283.dkr.ecr.ap-southeast-1.amazonaws.com/matters-web:latest matters-web:latest
See Conventions.
./public/static/
├── apple-touch-icon.png # favicons
├── favicon-16x16.png
├── icon-96x96.png
├── icons # icons in different sizes
│ ├── 12px
│ │ ├── ...
│ │ └── draft-edit.svg
│ ├── 72px
│ │ ├── ...
│ │ └── empty-warning.svg
│ └── stripe.svg
├── images # illustrations
│ ├── ...
│ └── publish-4.svg
├── manifest.json # configurations
└── opensearch.xml
We use SVGR to transform SVGs into React components. For reusability and bundle optimization:
- If the icon color isn't static:
- Replace the values of
fill
andstroke
attributes withcurrentColor
, and - Add
fill="none"
to<svg>
.
- Replace the values of
Apollo Client provides powerful caching capabilities that help optimize performance and user experience. There are 3 main approaches to update the cache:
- Automatic Updates: Apollo automatically updates the cache for entities with matching IDs
- Refetching Queries: Force refetch queries after mutations to ensure fresh data
- Manual Updates: Directly modify the cache for complex scenarios
const TOGGLE_PIN = gql`
mutation TogglePin($id: ID!, $pinned: Boolean!) {
editArticle(input: { id: $id, pinned: $pinned }) {
id
pinned
}
}
`
When to use: For simple mutations that modify a single entity's properties.
Key point: Make sure your mutation returns the same fields that your queries request, including the id
field.
// Update cache by evicting the old data and force to refetch the related queries
const [updateArticle] = useMutation(UPDATE_ARTICLE_MUTATION, {
update(cache, { data: { updateArticle } }) {
cache.evict({ id: cache.identify(article) })
cache.gc()
},
onQueryUpdated(observableQuery) {
return observableQuery.refetch()
},
})
// Or without mutation
client.refetchQueries({
updateCache: (cache) => {
cache.evict({ id: cache.identify(article) })
cache.gc()
},
include: ['ArticleDetailPublic'], // Optional: specify queries to refetch
})
// Update cache by refetching the given queries
// Note: We prefer `update` & `onQueryUpdated` over `refetchQueries`
// as we don't need to know which queries are affected by the mutation
const [updateArticle] = useMutation(UPDATE_ARTICLE_MUTATION, {
refetchQueries: [
'ArticleDetailPublic', // By query name
{ query: ARTICLE_DETAIL_PUBLIC, variables: { shortHash } }, // With variables
],
})
When to use: When mutations affect multiple entities or when you need to ensure the UI reflects the latest server state. Useful after complex operations like publishing content or when automatic updates aren't sufficient. This approach ensures all related pages and components are updated with the latest data.
// use readQuery and writeQuery
const [addComment] = useMutation(ADD_COMMENT, {
update(cache, { data: { addComment } }) {
const { article } = cache.readQuery({
query: GET_ARTICLE,
variables: { id: articleId },
})
cache.writeQuery({
query: GET_ARTICLE,
variables: { id: articleId },
data: {
article: {
...article,
comments: [...article.comments, addComment],
},
},
})
},
})
// use cache.modify
const [deleteComment] = useMutation(DELETE_COMMENT, {
update(cache, { data: { deleteComment } }) {
cache.modify({
id: cache.identify({ __typename: 'Article', id: articleId }),
fields: {
comments(existingComments = [], { readField }) {
return existingComments.filter(
(commentRef) => readField('id', commentRef) !== deleteComment.id
)
},
},
})
},
})
When to use: For operations that modify relationships between entities, when invalidating stale data. This approach is useful for updating the current page's data without requiring a full refetch.
- Optimistic Updates: Immediately update UI before server responds
- Cache Normalization: Apollo stores entities by ID to avoid duplication
- Pagination: Use field policies with
keyArgs
for paginated data - Type Policies: Configure how specific types are stored and merged
For more details, see the Mutations, Refetching queries, and Caching in Apollo Client.
See .vscode/settings.json
See .vscode/extensions.json
For vim users, you might want to see .vim/.vimrc
(using vim-plug).