What makes a good content type in JCR? Not the fanciest diagram. Not the biggest list of fields. The best models start with the questions your authors and apps need to answer. If you are on Apache Jackrabbit or AEM or Magnolia or Hippo CMS, the gear is ready. The trick is shaping nodes so your queries are boring and fast.
Start from questions, not from forms.
Start from queries, not fields
List the questions your site asks every minute. Latest news by tag. Product pages by SKU. Teasers for mobile under a certain word count. Let those questions drive your node type and property map. In JCR you win when the answer is a straight SQL2 or XPath line with no cartwheels. Fields come later. Queries first. That way you keep selectors simple, you keep joins rare, and you get cache friendly paths that Jackrabbit Oak can index cleanly.
If you cannot query it, you will end up scripting around it.
Define types that carry intent
Use CND to express intent. Keep it small. Use mixins for cross cutting features like tagging, SEO, and publishing dates. Keep binary blobs separate with nt:file when possible so backups and replication stay sane.
// file: /apps/com.example/nodetypes.cnd
<'com'='http://example.com/jcr/1.0'>
[com:article] > nt:unstructured, mix:versionable
- jcr:title (String) mandatory
- com:slug (String) mandatory unique
- com:summary (String) multiple
- com:body (String)
- com:tags (String) multiple
- com:author (String)
- com:publishedAt (Date)
- com:heroImage (Reference) > nt:base
+ com:content (nt:unstructured) = nt:unstructured protected
[com:taggable] mixin
- com:tags (String) multiple
[com:seo] mixin
- com:metaTitle (String)
- com:metaDescription (String)Two things to notice. First, slug is unique so you can route by slug with a single lookup. Second, authoring teams get flexibility inside a protected child called content for future blocks without breaking the top level shape. This keeps your main query fields at the top and the messy bits tucked away. Mix in com:seo only where you need it. Keep the core type tight.
Schemas are guard rails, not a cage.
Examples you can query without pain
// a compact article node
/content/site/en/news/2015/launch
jcr:primaryType = com:article
jcr:title = "We launched"
com:slug = "launch"
com:author = "Ana"
com:tags = ["release","product"]
com:publishedAt = "2015-04-01T09:00:00.000Z"
com:heroImage = <uuid-of-asset>// SQL2 queries that match the model
// latest articles with a tag
SELECT * FROM [com:article]
WHERE 'release' IN com:tags
ORDER BY com:publishedAt DESC
// lookup by unique slug
SELECT * FROM [com:article]
WHERE com:slug = 'launch'
// author feed with date window
SELECT * FROM [com:article]
WHERE com:author = 'Ana'
AND com:publishedAt > CAST('2015-01-01T00:00:00.000Z' AS DATE)
ORDER BY com:publishedAt DESCThese run clean on Oak when you back them with property indexes. Do not over index. Index the fields you filter and sort on and leave the rest alone.
// Oak property index example under /oak:index
/oak:index/articleSlug
jcr:primaryType = oak:QueryIndexDefinition
type = "property"
async = ["async"]
propertyNames = ["com:slug"]
declaringNodeTypes = ["com:article"]Mixins beat inheritance chains
Do not stack deep parent types to share fields. That turns into mystery meat. Use mixin sets like com:seo or com:taggable. You can add or remove them without hard migrations. Your authors will thank you and your queries stay readable. You also get freedom to evolve without breaking stored nodes, which in AEM or ModeShape keeps content packages small and safe.
Shallow beats clever every time.
Versioning, binaries, and paths
Turn on mix:versionable for text heavy types where rollbacks matter. Keep big binaries as separate nt:file under a known folder like /content/dam or /assets so you can control caching and offload to a CDN. Choose a path scheme that matches your navigation and your cache keys. Year and month in the path is fine if your editors care about date archives. If not, keep it short and slug based. Remember that path read is cheaper than a wide query.
When in doubt favor stable paths and indexed fields.
Common traps and quick fixes
Do not lean on nt:unstructured for everything. That is short term easy and long term pricey. Give your hot content a real type with declared properties so the repo can validate and index. Also avoid giant multivalue bags for tags and facets if you need to sort. Store sort keys as their own fields. If search is a first class feature, mirror key fields into a dedicated search node or push to Solr and keep JCR for truth and fast lookups.
Model for the reads you do one thousand times a day.
Shape your node types by the questions you ask and your JCR will feel simple.